深拷贝构造函数需手动分配新内存并逐元素复制,避免浅拷贝导致的悬垂指针;赋值运算符应采用拷贝-交换确保异常安全与自赋值正确;析构函数必须释放堆内存,三者(或五法则)须成对实现。

深拷贝构造函数必须手动分配新内存
默认生成的拷贝构造函数只做浅拷贝,对指针成员直接复制地址,导致两个对象指向同一块堆内存。一旦其中一个析构释放内存,另一个再访问就会 segmentation fault 或读到垃圾值。
实现要点:
- 检查源对象指针是否为
nullptr,避免解引用空指针 - 用
new(或std::make_unique)为每个元素分配独立内存,不能复用原对象的指针 - 若容器内是复杂类型(如嵌套自定义类),需确保其自身支持深拷贝(即有正确的拷贝构造函数)
- 注意异常安全:若中间某次
new抛出std::bad_alloc,已分配的内存要正确释放,否则内存泄漏
示例片段(假设容器持有一个动态数组 T* data_ 和长度 size_t size_):
MyContainer(const MyContainer& other) : size_(other.size_), data_(nullptr) {
if (other.data_) {
data_ = new T[size_];
for (size_t i = 0; i <h3>深拷贝赋值运算符要处理自赋值和资源释放</h3><p>默认赋值运算符同样浅拷贝,且不释放左操作数原有资源,造成内存泄漏+悬垂指针。自赋值(<code>a = a</code>)时若先释放再拷贝,会导致崩溃。</p><p>标准写法是「拷贝-交换」(copy-and-swap)或「先释放后重建」。推荐前者,更简洁且天然支持异常安全:</p>
- 在参数中接收
const MyContainer&,触发拷贝构造创建临时对象 - 调用
swap()交换当前对象与临时对象的内部指针 - 临时对象离开作用域自动析构,带走旧资源
- 无需显式检查自赋值——即使
a = a,拷贝构造生成的临时对象与a独立,交换后内容不变
关键前提是实现好 swap() 成员函数(只需交换指针和大小,O(1)):
void swap(MyContainer& other) noexcept {
std::swap(data_, other.data_);
std::swap(size_, other.size_);
}
<p>MyContainer& operator=(const MyContainer& other) {
MyContainer temp(other); // 深拷贝
swap(temp);
return *this;
}</p>
必须同时提供析构函数并遵守三法则(C++11 后是五法则)
只要写了自定义的拷贝构造、拷贝赋值或析构函数中的任意一个,编译器就不会生成移动版本(C++11 起),且默认析构函数可能无法正确释放堆内存。
所以这三者(或五者)应成对出现:
-
~MyContainer():负责delete[] data_(注意用[]匹配new[]) -
MyContainer(const MyContainer&):如上所述,深拷贝 -
MyContainer& operator=(const MyContainer&):如上所述,带异常安全 - C++11 起还应考虑:是否禁用或实现
MyContainer(MyContainer&&)和MyContainer& operator=(MyContainer&&),否则移动操作会退化为拷贝
常见错误:只写了拷贝构造但忘了析构函数,导致每次拷贝都新增内存却从不释放。
使用 std::vector 或智能指针可规避大部分问题
手写深拷贝逻辑容易出错,尤其涉及异常路径或嵌套结构时。现代 C++ 更推荐组合而非继承:
- 把
T*换成std::vector:它自动管理内存、深拷贝、异常安全,你只需实现容器外壳逻辑 - 若需裸指针语义,改用
std::unique_ptr:拷贝构造/赋值会被禁用(防止误浅拷贝),强制你显式定义深拷贝行为 - 避免在类中裸露
new/delete,除非明确需要极致控制或教学目的
例如:std::vector 成员让拷贝构造函数变成默认即可(= default),前提是 T 可拷贝。
深拷贝的真正难点不在语法,而在于所有权边界是否清晰——哪个对象负责哪块内存、谁在什么时候释放、异常发生时能否回滚。哪怕只漏掉一次 delete[] 或一次 nullptr 检查,运行时问题就很难复现。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











