浅拷贝是位拷贝,仅复制指针值而非内容,易致double free;深拷贝需自定义拷贝构造函数(A(const A&))和赋值运算符,用拷贝-交换法确保异常安全与自赋值防护,并遵循五法则实现资源管理。

浅拷贝就是位拷贝,深拷贝要自己写
默认的拷贝构造函数和赋值运算符只做浅拷贝:它逐字节复制对象内存,对指针成员来说,只是复制了指针值(地址),而不是指针指向的内容。如果原对象析构时 delete 了那块堆内存,副本再访问或析构就会触发 double free 或 use-after-free——这是最典型的崩溃根源。
深拷贝必须手动分配新内存、逐个复制数据。核心判断标准就一条:只要类里有指针成员(尤其是指向堆内存的)或资源句柄(如文件描述符、socket),就必须自定义拷贝构造函数和 operator=。
拷贝构造函数里别漏掉 const 引用参数
合法签名只能是 A(const A& other)。写成 A(A& other) 会导致无法接受临时对象(比如函数返回值),写成 A(A other) 会无限递归调用自身——因为传参会再次触发拷贝构造。
常见错误示例:
立即学习“C++免费学习笔记(深入)”;
A(A other) { // ❌ 错误:传值导致无限递归
data = other.data;
}
正确写法必须带 const &:
A(const A& other) : data(nullptr) {
if (other.data) {
data = new int[other.size];
std::copy(other.data, other.data + other.size, data);
size = other.size;
}
}
赋值运算符要处理自赋值和异常安全
浅拷贝的默认 operator= 同样只复制指针,而且不检查自赋值(a = a)。直接 delete data; data = other.data; 会导致 data 被删后又去读已释放内存。
推荐用“拷贝-交换”惯用法(copy-and-swap),天然规避自赋值和部分异常风险:
A& operator=(A other) { // 注意:这里传值,触发拷贝构造
swap(*this, other);
return *this;
}
void swap(A& a, A& b) noexcept {
std::swap(a.data, b.data);
std::swap(a.size, b.size);
}
关键点:
- 参数用值传递,让编译器自动调用你的拷贝构造函数
-
swap必须是noexcept,否则异常时可能破坏强异常保证 - 别在
operator=里手动delete再new,容易出错
移动语义出现后,深/浅拷贝逻辑依然不能省
C++11 后有了移动构造函数和移动赋值,但它们不会替代拷贝逻辑——只是提供更高效的资源转移方式。如果你没声明移动操作,编译器可能生成默认版本(对指针成员仍是浅移动),也可能因存在用户定义的拷贝操作而抑制移动函数生成。
真正安全的做法是:显式定义全部五个特殊成员函数(俗称“五法则”):
-
A(const A&)(深拷贝) -
A& operator=(const A&)(深赋值) -
A(A&&)(移动构造,通常把指针置为nullptr) -
A& operator=(A&&)(移动赋值) -
~A()(确保delete堆内存)
最容易被忽略的是:即使你写了移动操作,拷贝构造函数仍需保持正确——因为有些场景(如容器扩容时的元素复制)仍会调用它。










