什么是拷贝构造函数?
拷贝构造函数是一个特殊的构造函数,用于通过同类型的另一个对象来初始化新对象 如:
public Myclass{
public:
// 拷贝构造函数(参数是const引用)
MyClass(const MyClass& other) {
// 实现拷贝逻辑
}
};
何时会调用拷贝构造函数?
以下情况会触发拷贝构造函数调用:
-
用对象初始化对象
MyClass obj1; MyClass obj2 = obj1; // 调用拷贝构造函数 MyClass obj3(obj1); // 直接调用拷贝构造函数 -
函数参数按值传递对象
void func(MyClass obj) { ... } MyClass obj1; func(obj1); // 调用拷贝构造函数创建形参obj -
函数返回对象(某些情况下)
MyClass createObj() { MyClass obj; return obj; // 可能调用拷贝构造函数(编译器可能优化) }
默认拷贝构造函数的陷阱
-
如果未显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。
-
默认行为:浅拷贝(Shallow Copy),即逐成员复制(包括指针的值,而不是指针指向的内容)。
-
问题:如果类中有指针成员或动态分配的资源,浅拷贝会导致多个对象指向同一内存,引发问题:
- 内存泄漏:重复释放同一内存。
- 悬空指针:一个对象修改资源会影响其他对象。
-
示例:
class ShallowArray {
public:
int* data;
int size;
// 构造函数
ShallowArray(int size) : size(size) {
data = new int[size];
}
// 默认拷贝构造函数:浅拷贝 data指针被复制(指向同一内存)
// ~析构函数会delete[] data
};
int main() {
ShallowArray arr1(5);
ShallowArray arr2 = arr1; // 默认拷贝构造:arr2.data == arr1.data
// 析构时,arr1和arr2会重复释放同一内存,导致程序崩溃!
}
解决方案:自定义拷贝构造函数(深拷贝)
-
深拷贝(Deep Copy):在拷贝构造函数中,为新对象重新分配内存,并复制原对象指针指向的内容。
-
示例:
class DeepArray { public: int* data; int size; // 构造函数 DeepArray(int size) : size(size) { data = new int[size]; } // 自定义拷贝构造函数(深拷贝) DeepArray(const DeepArray& other) : size(other.size) { data = new int[size]; // 重新分配内存 for (int i=0; i<size; i++) { data[i] = other.data[i]; // 复制内容 } } // 析构函数 ~DeepArray() { delete[] data; } };
其他注意事项
(1)禁用拷贝构造函数
-
如果不希望对象被拷贝,可以将拷贝构造函数声明为
delete:class NonCopyable { public: NonCopyable(const NonCopyable&) = delete; };
(2)移动构造函数(C++11)
-
C++11引入了移动语义(Move Semantics),通过
移动构造函数和std::move()避免深拷贝的开销:class MyClass { public: // 移动构造函数 MyClass(MyClass&& other) noexcept { // 直接“窃取”other的资源(如指针) } };
(3)Rule of Three
- 如果一个类需要自定义拷贝构造函数、拷贝赋值运算符或析构函数中的任何一个,通常需要显式定义全部三者(C++11后扩展为Rule of Five,包括移动构造和移动赋值)。
6. 对比Java
- Java:对象赋值是引用传递,复制对象需要显式调用
clone()方法(需实现Cloneable接口)。 - C++ :拷贝构造函数控制对象复制的默认行为,更底层且灵活,但也需要开发者负责内存安全。
总结
- 拷贝构造函数用于控制对象初始化时的拷贝逻辑。
- 默认浅拷贝可能导致资源冲突,需通过深拷贝解决。
- 在涉及动态内存或资源管理时,必须显式定义拷贝构造函数!