## 使用c++改进出程序性能 ### 2.1右值引用 左值:表达式结束后依然存在的持久对象
右值:表达式结束时就不存在的临时对象
一个区分左值与右值的便捷方法:看能不能对表达式取地址,如果能则为左值,否则为右值。所有的具名变量或者对象都未左值,而右值不具名。
右值有两个概念构成,一个是将亡值,另一个是纯右值。 ### 2.1.1 &&的特性 右值引用就是对一个右值进行引用的类型。因为右值不具名,所以我们只能通过引用的方式找到它。 ``` template void f(T&& param); //T的类型需要推断,所以是个universal references template class Test{ Test(Test&& rhs); //已经定义类型,所以&&是个右值 }; void f(Test&& rhs); //同上 template void f(const T&& params); //&&是个右值,因为universal references仅仅在T&&下发生。 ``` T&&被左值或者右值初始化被称为引用折叠。
总结: 1. 左值和右值是独立于他们的类型的,右值引用类型可能是左值也可以是右值。 2. auto&&或是函数类型自动推导的T&&是一个未定的引用类型。 3. 所有的右值引用叠加到右值引用上仍然是右值引用,其他都是左值引用。当T&&为函数模板时,输入左值,它会变成左值引用,而输入右值时则会变成具名的右值引用。 4. 编译器会将已经命名的右值引用视为左值,而将未命名的右值引用视为右值。 ### 2.1.2 右值引用优化性能,避免深拷贝 对于含有堆内存的类,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,会导致堆内存的重复删除: ``` class A{ public: A():m_ptr(new int(0)) { } ~A() { delete m_ptr; } private: int* m_ptr; }; A Get(bool flag) { A a; A b; if(flag) return a; else return b; } int main() { A a=Get(false); //运行出错 } ``` 在上面的代码中,默认构造函数是浅拷贝,a和b会指向同一个指针m_ptr,在析构时会导致重复删除该指针,正确的方法是提供深拷贝函数。
``` A(const A& a):m_ptr(new int(*a.m_ptr)) { } ``` 但是有时这种拷贝构造函数是没有必要的,使用移动构造函数会更好。
``` A(A&& a):m_ptr(a.m_ptr) { a.m_ptr=nullptr; } ``` 这里的A&&用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。
需要注意的是:一般在提供右值引用的构造函数同时,也会提供左值引用的拷贝构造函数,以保证移动不成还可以用拷贝;提供了移动也同时提供拷贝构造函数。
有关拷贝构造函数相关的一些笔记:
拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。 当没有重载拷贝构造函数时,通过默认拷贝构造函数来创建一个对象 A a; A b(a); A b=a; 都是拷贝构造函数来创建对象b 强调:这里b对象是不存在的,是用a 对象来构造和初始化b的!! 浅拷贝:如果复制的对象中引用了一个外部内容(例如分配在堆上的数据),那么在复制这个对象的时候,让新旧两个对象指向同一个外部内容,就是浅拷贝。(指针虽然复制了,但所指向的空间内容并没有复制,而是由两个对象共用,两个对象不独立,删除空间存在)
深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。 拷贝构造函数重载声明如下: ``` A (const A&other) ``` 下面为拷贝构造函数的实现: ``` class A { int m_i A(const A& other):m_i(other.m_i) { Cout<<”拷贝构造函数”< 当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数。 当没有重载赋值函数(赋值运算符)时,通过默认赋值函数来进行赋值操作 A a; A b; b=a;
强调:这里a,b对象是已经存在的,是用a 对象来赋值给b的!! 赋值运算的重载声明如下: ``` A& operator = (const A& other) ``` 1. 拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。 ``` class A; A a; A b=a; //调用拷贝构造函数(b不存在) A c(a) ; //调用拷贝构造函数 /****/ class A; A a; A b; b = a ; //调用赋值函数(b存在) ``` 2. 一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象 3. 实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。 ### 2.2 move语义 普通左值也可以借助移动语义进行优化
c++11提供std::move方法将左值转化为右值,从而方便使用移动语义。即强制将左值转化为右值引用。 ``` { std::list tokens; std::list t=tokens; } std::list tokens; std::list t=std::move(tokens); ``` 事实上标准库容器都实现了move语义,而一些int之类的基础类型计算使用move,但仍然会发生拷贝。 ### 2.3 forward和完美转发 右值引用类型独立于值,一个右值引用参数作为函数的形参,在函数内部再转发该参数时它已经变成一个左值了。
所以我们需要“完美转发”,是指在函数模板中,完全依照模板的参数的类型(即保持参数的左值、右值特征),讲参数传递给函数模板中调用另一个函数。c++11中提供了std::forward ### 2.4emplace_back减少内存拷贝与移动 emplace_back能通过参数构造对象,不需要拷贝或者移动内存,相比push_back能够更好得避免内存的拷贝与移动,使得容器性能得到进一步提升。大多数情况下应该优先使用emplace_back。
其原理是直接利用构造函数直接构造对象
当然在push_back还不能完全被代替,比如在没有提供构造函数的的struct。