C++11 带来了一大批新特性,其中右值引用和移动语义是少数直接影响底层效率的改进。如果你工作里经常处理大对象——比如大字符串、大 vector——那这个特性用与不用,性能差距可能不止一个数量级。
下面用一个自定义链表的场景,把相关的概念和坑都串一下。
左值、右值,和它们怎么绑定引用
简单说,能取地址的就是左值,不能取地址的临时量或字面量就是右值。有了右值引用 &&,我们可以区分这两种对象,并分别做不同处理。
int a = 10;
int& lref = a; // 绑定左值
int&& rref = 10; // 绑定右值,同时延长了 10 的生命周期
// int& bad = 10; // 不行,非 const 左值引用不能绑右值
const int& good = 10; // const 左值引用可以,也能延长生命周期
这里有几个容易混淆的地方:右值引用变量本身是个左值。如果你有一个 int&& rr = 10;,那么 rr 在表达式里就是左值,可以赋值。这意味着如果需要转发它的'右值性',你得用 std::move 来显式转换。
移动构造与移动赋值:偷资源而不是拷贝
移动语义的核心是,当你明确知道某个对象即将被销毁(比如它是临时对象,或者你主动调了 std::move),就可以'偷走'它的资源,而不是拷贝一份。这通常通过移动构造函数和移动赋值运算符实现。
以一个简单的字符串类为例:
class MyString {
char* _str;
size_t _size;
public:
// 移动构造:拿过来,把原对象置空
MyString(MyString&& other) noexcept
: _str(other._str), _size(other._size) {
other._str = nullptr;
other._size = 0;
}
// 移动赋值:先把自己的资源释放,再偷,并置空源
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] _str;
_str = other._str;
_size = other._size;
other._str = nullptr;
other._size = 0;
}
return *;
}
};


