C++拷贝构造函数详解:从浅拷贝到深拷贝
什么是拷贝构造函数?
拷贝构造函数是C++中的一种特殊构造函数,用于创建一个新对象作为现有对象的副本。当我们使用一个对象来初始化同类型的另一个对象时,拷贝构造函数就会被调用。
基本语法
class MyClass {
public:
// 拷贝构造函数
MyClass(const MyClass& other) {
// 拷贝操作
}
};
何时调用拷贝构造函数?
拷贝构造函数在以下情况下被调用:
-
对象初始化:
MyClass obj1; MyClass obj2 = obj1; // 拷贝构造函数被调用 MyClass obj3(obj1); // 拷贝构造函数被调用 -
函数参数传递(按值传递):
void doSomething(MyClass obj) { // 函数体 } MyClass myObj; doSomething(myObj); // 拷贝构造函数被调用 -
函数返回值(按值返回):
MyClass createObject() { MyClass obj; return obj; // 可能调用拷贝构造函数(取决于编译器优化) }
默认拷贝构造函数的问题:浅拷贝
如果你没有自定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。这个默认实现执行浅拷贝(成员逐一复制),对于简单类型没问题,但对于指针成员会导致问题:
class ShallowCopy {
private:
int* data;
int size;
public:
ShallowCopy(int sz) : size(sz) {
data = new int[size];
}
// 默认拷贝构造函数执行浅拷贝:
// data = other.data; (复制指针值,而不是指向的内容)
// size = other.size;
~ShallowCopy() {
delete[] data;
}
};
使用这个类会导致双重释放问题:
ShallowCopy obj1(10);
ShallowCopy obj2 = obj1; // 浅拷贝:两个对象的data指向同一内存
// obj1和obj2析构时都会尝试释放同一块内存 → 崩溃!
解决方案:深拷贝
为了解决浅拷贝的问题,我们需要自定义拷贝构造函数来实现深拷贝:
class DeepCopy {
private:
int* data;
int size;
public:
DeepCopy(int sz) : size(sz) {
data = new int[size];
}
// 自定义拷贝构造函数(深拷贝)
DeepCopy(const DeepCopy& other) : size(other.size) {
data = new int[size];
for (int i = 0; i < size; ++i) {
data[i] = other.data[i]; // 复制内容,而不是指针
}
}
~DeepCopy() {
delete[] data;
}
};
完整的示例代码
#include <iostream>
#include <cstring>
class String {
private:
char* str;
int length;
public:
// 普通构造函数
String(const char* s = "") {
length = std::strlen(s);
str = new char[length + 1];
std::strcpy(str, s);
}
// 拷贝构造函数(深拷贝)
String(const String& other) : length(other.length) {
str = new char[length + 1];
std::strcpy(str, other.str);
std::cout << "拷贝构造函数被调用" << std::endl;
}
// 析构函数
~String() {
delete[] str;
}
// 显示字符串
void display() const {
std::cout << str << std::endl;
}
// 修改字符串
void update(const char* s) {
delete[] str;
length = std::strlen(s);
str = new char[length + 1];
std::strcpy(str, s);
}
};
int main() {
String s1("Hello");
String s2 = s1; // 拷贝构造函数被调用
std::cout << "s1: ";
s1.display();
std::cout << "s2: ";
s2.display();
s1.update("World"); // 修改s1,不影响s2
std::cout << "修改后:" << std::endl;
std::cout << "s1: ";
s1.display();
std::cout << "s2: ";
s2.display();
return 0;
}
拷贝构造函数与赋值运算符的区别
初学者常常混淆拷贝构造函数和赋值运算符,它们的区别在于:
- 拷贝构造函数:创建新对象时使用
- 赋值运算符:已存在的对象被赋予新值时使用
MyClass obj1;
MyClass obj2 = obj1; // 拷贝构造函数
MyClass obj3;
obj3 = obj1; // 赋值运算符
禁止拷贝
有时候我们想禁止对象的拷贝行为,C++11之前可以通过将拷贝构造函数声明为private来实现,C++11之后可以使用delete关键字:
class NonCopyable {
public:
NonCopyable() = default;
// 禁止拷贝
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
最佳实践
-
三分法则:如果你需要自定义析构函数、拷贝构造函数或拷贝赋值运算符中的任何一个,你可能需要自定义所有这三个。
-
考虑使用智能指针:现代C++中,使用
std::unique_ptr或std::shared_ptr可以避免许多手动内存管理的问题。 -
优先使用const引用传递对象:避免不必要的拷贝,提高性能。
-
考虑移动语义:C++11引入了移动构造函数和移动赋值运算符,对于管理资源的类,实现移动语义可以显著提高性能。
总结
拷贝构造函数是C++中管理对象复制行为的重要工具。理解浅拷贝和深拷贝的区别对于编写正确、安全的C++代码至关重要。对于包含动态分配资源的类,几乎总是需要自定义拷贝构造函数来实现深拷贝,避免潜在的内存问题和未定义行为。
在现代C++开发中,随着智能指针和移动语义的引入,我们需要手动实现拷贝构造函数的情况减少了,但理解其原理仍然非常重要。

浙公网安备 33010602011771号