记录篇-c++11特性学习-新语法2之改进程序性能

前言

本文是记录个人对c++11新特性的学习,文中所用可能会采用其他大神的图、思路、例子等,请大神们见谅。本博客文章是在学习过程的一些总结,整理出来,分享给大家,希望对各位读者有帮助,文章中的总结可能存在很多不完整或有错误的地方,也希望读者指出。

1、右值引用(R-value Referene)

主要目的:
1)消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率(避免深拷贝
2)能够更简洁明确地定义泛型函数
在C++11中可以取地址的、有名字的就是左值(非临时对象),反之,不能取地址的、没有名字的就是右值(临时对象)(将亡值或纯右值)
引用右值,使用&&来实现

int i = 0;  // 在这条语句中,i 是左值,0 是临时值,就是右值。
int &a = 2;       //左值引用绑定到右值,编译失败  在C++11之前,右值是不能被引用的,
int b = 2;        //非常量左值
const int &c = b; //常量左值引用绑定到非常量左值,编译通过
const int d = 2;  //常量左值
const int &e = c; //常量左值引用绑定到常量左值,编译通过
const int &b =2;  //常量左值引用绑定到右值,编程通过

///在C++11中,我们可以引用右值,使用&&来实现:
int &&a = 1;

纯右值主要有以下几种:
1)函数返回的非引用临时变量
2)运算表达式产生的临时变量值,比如上面的b + c
3)不和对象关联的字面量值,比如:1,‘a’,true
4)类型转换的返回值、lambda表达式

将亡值–将要被移动的对象:
1)函数返回值为T&&的返回对象
2)std::move的返回值
3)类型转换为T&&的函数返回值

和左值引用的区别:
1)绑定的对象(引用的对象)不同,左值引用绑定的是返回左值引用的函数、赋值、下标、解引用、前置递增递减
2)左值持久,右值短暂,右值只能绑定到临时对象,所引用的对象将要销毁或该对象没有其他用户
3)使用右值引用的代码可以自由的接管所引用对象的内容

右值引用总结
1)左值和右值是独立它们的类型的,右值引用类型可能是左值也可能是右值
2)auto&&或者函数参数类型自动推导的T&&是一个未定的引用类型(左值或者右值),取决于初始化的类型。
3)所有的右值引用叠加到右值引用上仍然是右值引用,其他叠加是左值引用;当T&&为模板参数,输入左值(或右值),则它会变成左值引用(或具名的右值引用)
4)编译器会将已命名的右值引用作为左值,而将未命名的右值引用作为右值

2、移动语义和std::move

当一个类中含有指针成员时,由于默认的拷贝构造函数只会进行浅拷贝

Base getBase()
{
    return Base();       // 无参构造一次,临时变量拷贝构造一次
}

int main()
{
    Base a = getBase();  // 拷贝构造第二次
    return 0;
}
//拷贝构造函数一共被调用了两次,申请空间又释放内存,效率比较低

C++11中新增了移动构造函数,可以避免这个问题:

Base(Base&& base): data(base.data){ base.data = nullptr; }

即移动构造函数接受一个”右值引用”参数
类是否可移动时,可以借助模板类来判断:

std::is_move_constructible<T>::value;

注意点
1)移动语义一定是要修改临时变量的值
2)移动构造函数抛出异常会导致部分指针悬挂,通过noexcept关键字保证抛出异常直接终止
3)用std::move_if_noexcept函数替代move函数,这是牺牲性能保证安全的一种做法

右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,但左值不会被析构,常常被用来在移动构造函数中。

int a;
int &&r1 = c;             # 编译失败
int &&r2 = std::move(a);  # 编译通过,std::move等同于static_cast<T&&>(lvalue);

举例:高效使用swap函数

template<typename T>
void swap(T& a, T& b)
{
    T temp(std::move(a));
    a = std::move(b);
    b = std::move(temp);
}

3、完美转发和std::forward

在函数模板中,完全按照模板参数的类型将参数传递给模板函数中调用的另一个函数,并且不产生额外开销,举例如下:

template<typename T>
void func(T t)
{
    other_func(t);
}

//该模板函数把t”转发”给了另一个函数,但是这种方法会在t传入的时候生成一个临时变量,会产生额外开销
void other_func(int t){}
template<typename T>
void func(const T& t)
{
    other_func(t);
}

C++11中,新增了引用折叠(reference collapsing)的规则,并结合新的模板推导规则来完成完美转发

typedef const int T;
typedef T& TR;
TR& tr = 1;      // 在C++98中编译失败,C++11中编译通过  TR&其实最终被认为是int&

在这里插入图片描述
完美转发规律
1)没有对应的非常量函数,就会调用常量函数
2)没有对应的右值引用,就会调用对应的左值引用

std::forward专门用于转发
右值引用类型是独立于值的,一个右值引用参数作为函数的形参,在函数内部再转发该参数的时候它已经变成一个左值,并不是他原来的类型

template<typename T>
void print(T& t){
    cout << "lvalue" << endl;
}

template<typename T>
void print(T&& t){
    cout << "rvalue" << endl;
}

template<typename T>
void TestForward(T && v){
    print(v);
    print(std::forward<T>(v));
    print(std::move(v));
}

int main(){
    TestForward(1);
    int x = 1;
    TestForward(x);
    TestForward(std::forward<int>(x));
    return 0;
}

4、std::emplace_back

原文:https://blog.csdn.net/p942005405/article/details/84764104
能通过参数构造对象,不需要移动或者拷贝内存,使得容器插入元素性能进一步提升

template <class... Args>
void emplace_back (Args&&... args);

4.1、使用注意点

1)不直接给emplace_back传递ivec.back()

#include <vector>
#include <string>
#include <iostream>
using namespace std;
 
int main()
{
    vector<int> ivec;
    ivec.emplace_back(1);
    ivec.emplace_back(ivec.back());
    for (auto it = ivec.begin(); it != ivec.end(); ++it)
        cout << *it << " ";
    return 0;
}
 
//输出:
1 -572662307 

2)不给emplace_back传递引用

#include <vector>
#include <string>
#include <iostream>
using namespace std;
 
int main()
{
    vector<int> ivec;
    ivec.emplace_back(1);
    auto &it = ivec.back();
    ivec.emplace_back(it);
    for (auto it = ivec.begin(); it != ivec.end(); ++it)
        cout << *it << " ";
    return 0;
}
输出:
1 -572662307 

3)避免emplace_back引起重新分配内存

#include <vector>
#include <string>
#include <iostream>
using namespace std;
 
int main()
{
    vector<int> ivec;
    ivec.reserve(4);
    ivec.emplace_back(1);
    ivec.emplace_back(ivec.back());
    for (auto it = ivec.begin(); it != ivec.end(); ++it)
        cout << *it << " ";
    return 0;
}
输出:
1 1

4)当结构体中没有提供相应的构造函数就不能使用emplace,该使用push_back

4.2、与push_back() 的区别

区别在于底层实现的机制不同:
1)push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);
2)push_back() 在底层实现时,会优先选择调用移动构造函数,如果没有才会调用拷贝构造函数
3) emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程

5、unordered container(无序容器)

无序容器中的元素是不进行排序的,内部通过 Hash 表实现(有序容器是红黑树实现),插入和搜索元素的平均复杂度为 O(constant),在不关心容器内部元素顺序时,能够获得显著的性能提升。
C++11 引入了两组无序容器:std::unordered_map/std::unordered_multimap 和 std::unordered_set/std::unordered_multise

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值