C++11标准中新增的右值引用和移动构造函数是移动语义的核心组成部分,二者的结合使用可以有效减少对象拷贝带来的性能损耗,尤其在处理包含动态分配资源的对象时效果显著。

右值引用的基本概念
右值引用是C++11引入的新的引用类型,使用&&符号声明,专门用于绑定右值。右值指的是临时对象、字面量或者即将被销毁的对象,比如函数返回的临时对象、表达式计算结果等。与左值引用&不同,右值引用可以延长临时对象的生命周期,让开发者可以安全地获取临时对象的资源。
常见的右值包括:
- 字面量,比如
10、3.14 - 函数返回的临时对象,比如
std::string("test")的返回值 - 使用
std::move转换后的左值,转换后左值会变成右值引用
移动构造函数的实现原理
移动构造函数是一种特殊的构造函数,它的参数是该类的右值引用,作用是“窃取”右值对象的资源,而不是像拷贝构造函数那样重新分配资源并复制内容。移动构造函数执行后,被移动的对象会处于一个有效但未定义的状态,通常其内部的资源指针会被置为空,避免资源被重复释放。
下面是一个包含动态分配资源的类的移动构造函数示例:
#include <iostream>
#include <cstring>
class MyString {
private:
char* data;
int size;
public:
// 普通构造函数
MyString(const char* str = nullptr) {
if (str) {
size = strlen(str);
data = new char[size + 1];
strcpy(data, str);
} else {
size = 0;
data = nullptr;
}
}
// 拷贝构造函数(深拷贝)
MyString(const MyString& other) {
size = other.size;
if (other.data) {
data = new char[size + 1];
strcpy(data, other.data);
} else {
data = nullptr;
}
std::cout << "拷贝构造函数被调用" << std::endl;
}
// 移动构造函数
MyString(MyString&& other) noexcept : data(other.data), size(other.size) {
// 窃取other的资源
other.data = nullptr;
other.size = 0;
std::cout << "移动构造函数被调用" << std::endl;
}
// 析构函数
~MyString() {
if (data) {
delete[] data;
}
}
void print() const {
if (data) {
std::cout << data << std::endl;
} else {
std::cout << "空字符串" << std::endl;
}
}
};
右值引用与移动构造函数的结合使用场景
1. 函数返回临时对象
当函数返回一个临时创建的对象时,如果该对象有移动构造函数,编译器会优先调用移动构造函数而不是拷贝构造函数,减少资源拷贝的开销。
MyString createString() {
MyString temp("Hello World");
return temp; // 这里会触发移动构造(如果编译器没有做返回值优化的话)
}
int main() {
MyString str = createString();
str.print(); // 输出Hello World
return 0;
}
2. 使用std::move转换左值为右值
对于左值对象,如果该对象之后不再被使用,可以使用std::move将其转换为右值引用,从而触发移动构造函数的调用,避免不必要的拷贝。
int main() {
MyString str1("Test");
// 使用std::move将左值str1转换为右值引用,触发移动构造
MyString str2(std::move(str1));
str2.print(); // 输出Test
str1.print(); // 输出空字符串,str1的资源已经被转移
return 0;
}
3. 容器中的元素操作
STL容器在C++11之后都支持了移动语义,当向容器中插入临时对象或者移动后的左值对象时,会调用移动构造函数,提升插入效率。比如在std::vector的push_back操作中,如果传入的是右值,就会触发移动构造而不是拷贝构造。
#include <vector>
int main() {
std::vector<MyString> vec;
vec.push_back(MyString("临时对象")); // 触发移动构造
MyString str("左值对象");
vec.push_back(std::move(str)); // 触发移动构造
return 0;
}
使用注意事项
- 移动构造函数建议声明为
noexcept,这样容器在进行扩容等操作时,可以安全地使用移动构造函数,否则可能会回退到拷贝构造函数。 - 被移动后的对象不能再进行依赖其内部资源的操作,因为它的资源已经被转移,通常在使用
std::move之后,不应该再使用该对象,除非重新给它赋值。 - 如果类没有定义移动构造函数,编译器会尝试生成默认的拷贝构造函数来代替,此时移动语义不会生效,因此需要手动定义移动构造函数来启用移动语义。
- 右值引用本身是左值,所以在移动构造函数内部,参数
other是左值,需要再次使用std::move才能触发其他移动操作,比如other.data = std::move(other.data)。