Move语义
在 C++11 以前,只能使用一种方式来传递对象——拷贝构造(赋值)。为了安全,若对象同时拥有栈和堆上数据时,我们会手动实现这个函数,将堆上数据再拷贝一份,这种深拷贝在多次赋值时拖慢了效率。
C++11 后,为了实现只拷贝栈上数据,不拷贝堆上数据(浅拷贝,又称移动)的语义,在原先左右值的基础上进行了扩展,引入了将亡值(xvalue)。于是现在有了三种值类型——左值(lvalue)纯右字面值(prvalue)将亡值(xvalue)。
先不考虑 C++ 为啥引入这个,想想该如何设计浅拷贝这个需求?理想状态下,应该是调用一个类似 a.move_to(_T& b)
的方法,使 a
让出堆上数据的所有权(指针地址)给 b
,然后自动析构,再也不使用 a
。这样似乎更符合 “谁的数据谁做主” 的思想,但是由于拷贝构造函数使用了类似 b.copy_from(const _T& a)
的语法,于是为了统一,这便是 C++ 给出的答案:b.move_from(_T&& a)
,在 b
的该函数中转移指针,并析构掉在栈上的 a
。
这就有点抢夺别人的数据的意思,但是意义上是等价的。那么为了和拷贝区别开,C++ 特化了这种构造(赋值)函数,其参数只能接受右值,优先级别比拷贝高,也就是说”能移动就移动“。
那么,如果是函数表达式返回值这一类不具名的值,它显然是右值,直接移动就好,显然提升了效率。但是如果已经有了一个对象 a
,需要浅拷贝到某个容器中,并不再使用 a
(非常不建议保留原来的指针并使用,除非你知道自己在做什么),直接 _T b(a)
显然是不行的,因为 a
是左值,这样只会调用拷贝构造。
于是 std::move(a)
闪亮登场。其作用仅仅是类型转换,将左值变量 a
转换成右值(xvalue)。请注意这个函数对 a
内部没有任何影响。相当于是给 a
打了 tag 表示 a
现在是右值,可以移动了,但并不会进一步操作。操作是接受右值的函数内部进行的。
总结:
- xvalue可以被移动构造函数直接接受;
- 移动构造lvalue,那么需要先使用
std::move
将lvalue变成xvalue,从而不调用拷贝,而调用移动; - prvalue被接受的时候,生成了一个临时变量xvalue,函数内操作是对这个xvalue进行(这个无所谓,重要的是上面)