- 深拷贝的性能问题
- 右值引用
- 使用&&来声明方法的参数为右值引用类型
- 移动语义的move方法
- forward 完美转发
- emplace_back
如果一个类的构造函数申请了堆内存以存储数据,则必然需要在析构函数中释放堆内存,同时需要定义复制构造函数和重载赋值运算符,实现堆内数据的深拷贝。否则可能导致堆内存的二次释放。
但有时候这种深拷贝对于我们的程序来说是非必要的,而且如果存储在堆内的数据量非常大,深拷贝会引起很大的额外性能开销。此时,我们希望有一种方法来避免总是执行非必要的深拷贝。
C++11引入了右值引用和移动语义,来避免无意义的深拷贝,提高程序性能。
右值引用左值是表达式结束后仍然存在的持久对象,右值是指表达式结束时就不存在的临时对象。
一个简单区分方法:如果可对表达式用&符取址,则为左值,否则为右值。
右值没有具体名字,只能通过引用来找到它。
使用&&来声明方法的参数为右值引用类型先看下面代码:
class A
{
public:
A()
{
cout << "# a new A by nativen";
n = new int[5];
}
~A()
{
cout << "delete An";
delete [] n;
}
A(const A & a)
{
cout << "# a new A by copyn";
n = new int[5];
memcpy(n, a.n, 5); // 深拷贝
}
A(A && a)
{
cout << "# a new A by moven";
n = a.n; // 浅拷贝
a.n = nullptr;
}
private:
int *n;
};
// 这样子写是为了避免返回值被优化而导致无法出现右值引用
A getA(bool flag)
{
cout << "##### into getA nown";
A a;
A b;
cout << "##### return nown";
if(flag)
return a;
else
return b;
}
int main()
{
{
cout << "----- copy testn";
A a1;
A a2(a1); // need copy
}
cout << "----- copy overn";
{
cout << "----- move testn";
A a3 = getA(true); // no copy, just move
}
cout << "----- move overn";
}
输出为:
----- copy test # a new A by native # a new A by copy # 此处调用了复制构造函数,进行深拷贝 delete A delete A ----- copy over ----- move test ##### into getA now # a new A by native # a new A by native ##### return now # a new A by move # 函数的返回是一个临时变量,显然属于右值,因此对应地调用参数是右值引用的构造函数A(A && a) delete A delete A # 由于是引用,getA()函数返回的对象此时才被释放 delete A ----- move over
可见,利用右值引用定义的构造函数并不需要执行深拷贝,并且由于右值在外部并不会被引用到,所以在构造函数中将其资源直接转移出来是安全的。这就是所谓的移动语义(move),右值引用的一个重要目的是用来支持移动语义。对应的构造函数称为移动构造函数。一般来说还需要进一步重载采用右值引用的赋值运算符,称为移动赋值运算符。
移动语义的move方法C++11提供了std::move()方法来将左值转换为右值,从而可以使得一般的左值也能够利用移动语义来优化性能。
move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。
示例:
int main()
{
A a1;
A a2 = move(a1);
}
输出:
# a new A by native # a new A by move # 此时a1已不再拥有资源 delete A delete Aforward 完美转发
当一个模板函数的参数类型定义为&&右值引用(void func(T && n)),可以传入左值也可以传入右值。当一个右值引用传入该函数时,他在实参中有了命名n,那么此时如果继续向下调用其他函数(将n转发给其他函数),根据C++ 标准的定义,这个参数变成了一个左值。
如下所示,将1作为右值传入后,再将其传入 print,走的却是 print 的左值版本。
templatevoid print(T && n) { cout << "R value " << n << endl; } template void print(T & n) { cout << "L value " << n << endl; } template void func(T && n) { print(n); } int main() { cout << "----------- func(1)n"; func(1); }
输出打印:
----------- func(1) L value 1
如果继续向下转发的函数其左值版本存在深拷贝,则其对此优化的右值引用版本将不会被调用。此时可以引入完美转发std::forward,恢复变量原来的引用属性。如果n传入前原来是左值,就恢复为左值,如果原来是右值就恢复为右值。
templatevoid func(T && n) { print(n); print(forward (n)); // 完美转发-原来是右值,则转回右值 } int main() { int n = 10; cout << "----------- func(1)n"; func(1); cout << "----------- func(n)n"; func(n); // n 是左值 }
----------- func(1) L value 1 # print(n); R value 1 # print(forwardemplace_back(n)); ----------- func(n) L value 10 L value 10 # after forward , L is still L
对于STL容器,C++11后引入了emplace_back接口,可用于替代push_back,提高性能。
考虑下面的代码:
vectorvs; vs.push_back("haha");
尽管 push_back 已经实现了右值引用的版本,但其调用过程中,传入字符串"haha"本身需要先构造一个string临时对象,然后vector内部又要调用一次移动构造函数,完了之后将传入的string临时对象析构。虽然比起传入左值引用而调用复制构造函数的方式要快,但构造和析构这个临时对象仍然显得多余。
c++11可以用 emplace_back 代替 push_back ,emplace_back 可以直接在 vector 中直接构建一个对象,而非创建一个临时对象再放进 vector
所以,以后应尽量使用emplace_back :
vectorvs; vs.emplace_back("haha");


![[C++] 右值引用和移动语义 [C++] 右值引用和移动语义](http://www.mshxw.com/aiimages/31/347085.png)
