直接复制指针会导致浅拷贝,两个对象共享同一堆内存,析构时引发double free或use-after-free等未定义行为;安全做法是深拷贝:new新内存并复制原数据。

拷贝构造函数里直接复制指针会出什么问题
直接用 other.ptr 赋值给当前对象的指针成员,会导致两个对象指向同一块堆内存。一旦其中一个析构时调用 delete ptr,另一个对象的指针就变成悬空指针——后续访问或再次释放就会触发未定义行为,常见表现是程序崩溃、地址错误(double free 或 use-after-free)。
这种问题在资源管理类中尤其典型,比如自己封装的字符串类、动态数组类。只要类里有 new 出来的裸指针,又没重写拷贝构造函数,就大概率中招。
- 默认拷贝构造函数只做浅拷贝,对指针就是复制地址值
- 析构函数若含
delete ptr,而两个对象共享该指针,第二次delete就崩 - 即使没立即崩溃,读写同一内存也可能引发数据错乱,更难调试
怎么写安全的拷贝构造函数
核心是深拷贝:为新对象重新分配内存,并把原对象的数据复制过去。不是简单复制指针,而是复制它指向的内容。
假设类有一个 int* data 和 size_t len:
立即学习“C++免费学习笔记(深入)”;
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
MyClass(const MyClass& other) : len(other.len) {
data = new int[other.len];
for (size_t i = 0; i < len; ++i) {
data[i] = other.data[i];
}
}- 先分配新内存:
new int[other.len],不能复用other.data - 逐字节/逐元素复制内容,别漏掉边界(
i ) - 确保
len成员也被正确初始化,否则后续delete[]可能越界 - 如果原指针可能为
nullptr,要加判空,否则new前解引用会崩
为什么移动构造函数能绕过这个问题
移动构造函数不复制数据,而是“接管”资源所有权,把原对象的指针设为 nullptr,避免双重释放。
它解决的是性能和安全的双重问题:既避免深拷贝开销,又天然规避了浅拷贝陷阱。
- 典型写法:
data = other.data; other.data = nullptr; - 必须配套修改析构函数:检查
if (data) delete[] data; - 移动后原对象处于有效但未定义状态,不能再访问
data,否则读nullptr或野指针 - 编译器只在明确是右值时调用移动构造(如临时对象、
std::move()),不会替代所有拷贝场景
现代 C++ 更推荐怎么处理
裸指针 + 手动内存管理本身就是高危路径。标准库容器和智能指针才是更稳的选择。
把 int* data 换成 std::vector<int> data</int>,或者用 std::unique_ptr<int></int>,拷贝构造逻辑就由标准库兜底,不用自己操心深拷贝、异常安全、noexcept 声明这些细节。
-
std::vector拷贝构造自动深拷贝,且支持异常安全 -
std::unique_ptr默认禁止拷贝,强制你面对所有权问题;如需共享,显式用std::shared_ptr - 如果非用裸指针不可,至少确保三法则(拷贝构造、拷贝赋值、析构)全部显式定义,且行为一致
裸指针本身没问题,问题出在把它当成普通值来拷贝。真正容易被忽略的,是拷贝构造函数和析构函数之间的契约关系——它们必须共同维护同一份资源的生命周期,差一环就全盘失效。

















