C++ 移动语义与右值引用详解
理解
移动语义概念
移动语义是 C++11 新增的语法特性,目的是将一个对象的资源移动给另一个对象,以减少拷贝的成本。
在官方文档中主要涉及 std::move、右值、移动构造的相关章节,共同构成了移动语义。
std::move 的原理
一般问移动语义都会问 std::move 的实现,其实非常简单,就是一个类型转化,将传入的参数类型转化为右值引用。
简化一下就是:
template<class T>
constexpr std::remove_reference<T>&& move(T& arg) noexcept {
return static_cast<std::remove_reference<T>&&>(arg);
}
constexpr 表示这个函数在编译期就能确定返回值,进一步表明这个函数没有任何运行时开销。
资源的实际移动由谁实现
既然 std::move 只做了类型转化,谁来实现资源的移动呢?就是移动拷贝构造和移动赋值构造函数。
C++11 之后,编译器默认生成这两个函数,当然默认生成的版本依旧都是值拷贝。
因此如果自定义类里有堆上的成员,需要自己写一下这两个函数,这里简单做个示例:
struct BigObj {
int* arr = nullptr;
BigObj(BigObj&& o) : arr(o.arr) {
o.arr = nullptr;
}
};
直接把原对象的指针复制到当前对象,然后再把原对象的指针置空,这样就实现了资源的转移。
关于 return 与 NRVO 的误区
移动语义作用很多,但首先讲一个误区:很多人学了 std::move 后,马上就在值返回函数的 return 语句中用上了,但他们忘了去考虑编译器的优化,具体对应官方文档的【复制省略】章节:
具有类返回类型的函数中的 return 语句中,当操作数是非 volatile 且具有自动存储期的对象 obj 的名称时(函数参数或处理函数参数除外),可以通过将 obj 直接构造到函数调用的结果对象中来省略结果对象的复制初始化。这种副本消除的变体称为命名返回值优化 (NRVO)。
简单讲 NRVO 就是编译器把'return 本地变量'变成'直接在返回值位置上造变量'。那么当编译器有 NRVO 时,return std::move(x) 通常是多此一举,甚至可能降低性能,因为 move 后会把'零拷贝'强制变成一次移动。
而对于没有 NRVO 的情况,只要变量是可移动的,C++11 起就会默认先尝试用移动构造。
所以:别对局部返回值用 std::move,直接 return x 就行。
std::move 的主要用途:传参
std::move 主要还是用于传参,因为它能调用对应的移动构造函数,实现资源的转移。比如 lambda 构造时,可以直接捕获一个超大的对象;启动一个线程时,可以将资源转移到对应的线程,真正意义的独占资源,不需要加锁保护。


