浅拷贝引发的双重释放是指类含指针成员且未定义拷贝构造函数时,默认位拷贝使多个对象共享同一堆内存,析构时多次delete同一地址导致崩溃;须同时实现拷贝构造、拷贝赋值(含自赋值检查)和析构函数,或改用std::vector等标准容器避免手动管理。
当类里有指针成员且没定义拷贝构造函数时,C++ 默认执行位拷贝(浅拷贝):两个对象的指针成员指向同一块堆内存。一旦其中一个对象析构,delete 了这块内存,另一个对象再析构时再次 delete 同一地址,触发未定义行为——常见表现是程序崩溃、报错 double free or corruption。
只写拷贝构造函数还不够,因为 = 赋值也会触发浅拷贝。必须同时实现:
MyClass(const MyClass& other) —— 拷贝构造函数MyClass& operator=(const MyClass& other) —— 赋值运算符(注意自赋值检查)~MyClass() —— 析构函数中 delete 指针三者缺一不可,否则仍可能出问题。现代 C++ 推荐用 Rule of Five,但最基础的“三法则”(拷贝构造、拷贝赋值、析构)已能解决该问题。
核心是为每个对象独立分配并复制数据,而不是共享指针。以含 int* 成员的类为例:
class DataHolder {
private:
int* data_;
size_t size_;
public:
DataHolder(sizet n) : size(n), data_(new int[n]{}) {}
// 拷贝构造函数:分配新内存 + 逐字节复制
DataHolder(const DataHolder& other)
: size_(other.size_
), data_(new int[other.size_]) {
for (size_t i = 0; i < size_; ++i) {
data_[i] = other.data_[i];
}
}
// 拷贝赋值:先释放旧资源,再深拷贝(注意自赋值)
DataHolder& operator=(const DataHolder& other) {
if (this == &other) return *this;
delete[] data_;
size_ = other.size_;
data_ = new int[other.size_];
for (size_t i = 0; i < size_; ++i) {
data_[i] = other.data_[i];
}
return *this;
}
~DataHolder() { delete[] data_; }};
关键点:
data_(other.data_) —— 这是浅拷贝this == &other,否则 a = a 会先 delete[] 再访问已释放内存new int[n] 就必须配 delete[],混用 delete 会导致未定义行为手动管理裸指针极易出错。绝大多数场景下,直接用 std::vector 或 std::string 替代 int* 和 char*:
class DataHolder {
private:
std::vector data_;
public:
DataHolder(sizet n) : data(n) {}
// 无需自定义拷贝构造、赋值、析构 —— vector 已实现深拷贝语义
// 编译器生成的默认版本完全安全
};
这样既避免双重释放,又省去大量样板代码。只有在性能极端敏感、或必须与 C API 交互时,才考虑手动管理动态内存。
真正容易被忽略的是:即使写了深拷贝,也常忘记同步更新赋值运算符,或者漏掉自赋值检查;而用 std::vector 后,这些风险天然消失。