在C++中,拷贝构造函数和移动构造函数都是构造函数的特殊形式,用于创建类对象的副本或转移对象的资源。这两者的用法和目的不同,各有其使用场景。
1. 拷贝构造函数
拷贝构造函数用于创建一个对象的副本,复制对象的成员数据。
语法
class MyClass {
public:
MyClass(const MyClass& other); // 拷贝构造函数的声明
};
默认拷贝构造函数
如果类没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。默认的拷贝构造函数会逐个复制对象的每个成员,称为“浅拷贝”。
自定义拷贝构造函数
在某些情况下,如类中有指针成员或需要深拷贝时,需要自定义拷贝构造函数:
class MyClass {
int* data;
public:
MyClass(int val) : data(new int(val)) {}
// 自定义拷贝构造函数,执行深拷贝
MyClass(const MyClass& other) {
data = new int(*other.data); // 分配新内存并复制值
}
~MyClass() {
delete data;
}
};
使用场景
当一个对象需要被复制时,会调用拷贝构造函数,例如:
- 对象传递到函数时按值传递
- 对象从函数返回时按值返回
- 用一个对象初始化另一个对象时
MyClass obj1(5);
MyClass obj2 = obj1; // 调用拷贝构造函数
2. 移动构造函数
移动构造函数在C++11中引入,用于高效地“移动”对象的资源,而不是复制。它避免了不必要的资源分配和数据复制,特别适合处理临时对象或大数据对象。
语法
class MyClass {
public:
MyClass(MyClass&& other); // 移动构造函数的声明
};
默认移动构造函数
如果类没有定义移动构造函数,编译器会在可能的情况下自动生成默认的移动构造函数。默认移动构造函数执行成员的“移动”,通常是浅移动(将指针或资源的所有权转移)。
自定义移动构造函数
如果类中有动态分配的资源(如指针),可能需要自定义移动构造函数,以避免重复释放资源。
class MyClass {
int* data;
public:
MyClass(int val) : data(new int(val)) {}
// 自定义移动构造函数
MyClass(MyClass&& other) noexcept {
data = other.data; // 转移资源的所有权
other.data = nullptr; // 避免原对象释放资源
}
~MyClass() {
delete data;
}
};
使用场景
移动构造函数在以下场景下会被调用:
- 将一个临时对象赋给另一个对象时
- 通过
std::move
将一个对象显式地转换为右值引用 - 从函数返回一个对象时
MyClass obj1(5);
MyClass obj2 = std::move(obj1); // 调用移动构造函数
3. 区别与用法
拷贝构造 vs 移动构造
- 拷贝构造会深度复制对象的所有成员变量,适用于要创建对象副本的场景。
- 移动构造会将资源从一个对象转移到另一个对象,而不是复制,避免了深拷贝的性能开销。
拷贝构造的典型场景:
- 当你需要保留源对象并同时创建一个新对象时,例如函数按值传递、按值返回。
- 适用于类中成员变量不涉及资源管理(如指针、动态内存)时。
移动构造的典型场景:
- 处理临时对象时,临时对象只需短期使用,然后资源可以转移。
- 用于大数据对象的转移操作(如容器、文件句柄等),避免不必要的资源分配。
举例
拷贝构造函数:
MyClass obj1(10); // 普通构造
MyClass obj2(obj1); // 拷贝构造,obj2 是 obj1 的副本
移动构造函数:
MyClass obj1(10);
MyClass obj2(std::move(obj1)); // 移动构造,obj2 拥有 obj1 的资源,obj1 被置为空
4. 拷贝/移动构造函数的选择顺序
编译器会根据以下优先级选择构造函数:
- 如果能移动,编译器优先使用移动构造函数。
- 如果不能移动(如对象为
const
或没有移动构造函数),则使用拷贝构造函数。
5. 总结
- 拷贝构造函数用于创建对象的副本,通常涉及深拷贝的情形。
- 移动构造函数则用于转移资源,避免了不必要的深拷贝,提高了性能。
- 在资源管理类中,通常需要同时定义拷贝构造函数、移动构造函数,以及相应的析构函数来确保资源的正确管理