浅拷贝是默认的位拷贝,会导致指针成员共享同一堆内存,引发use-after-free或double free;深拷贝需手动分配新内存并复制内容,且拷贝构造函数与operator=必须成对实现。

浅拷贝就是默认拷贝,但多数时候它在悄悄搞破坏
默认拷贝构造函数和赋值运算符只做位拷贝(bitwise copy),对指针成员不做任何特殊处理——这意味着两个对象的指针变量指向同一块堆内存。一旦其中一个对象析构时 delete 了那块内存,另一个对象再访问就会触发 use-after-free 或二次释放(double free),程序大概率崩溃或行为未定义。
常见错误现象:Segmentation fault、malloc: *** error for object ...: pointer being freed was not allocated、数据莫名被改写。
- 只要类里有
new出来的指针(比如int*、std::string*、自定义结构体指针),就必须自己写拷贝构造函数和operator= - 即使没显式写指针,用了
std::vector、std::string等容器也不用担心——它们内部已实现深拷贝,属于“安全成员” - 注意:移动语义(
T(T&&))不会触发浅拷贝问题,但它会把原对象“掏空”,这和拷贝是两回事
深拷贝必须手动分配新内存,别信编译器自动帮你
深拷贝的本质是:为每个动态资源单独申请新内存,并把原内容完整复制过去。这不是靠改个参数或加个关键字能解决的,必须写代码。
实操建议:
立即学习“C++免费学习笔记(深入)”;
- 拷贝构造函数里对每个指针成员调用
new(或new[]),再用循环或std::copy复制内容 - 记得同步拷贝“长度”“容量”等元信息(比如
size_、capacity_),否则新对象可能读越界 - 如果类里有多个指针,漏掉任何一个都会导致部分浅拷贝 —— 这类 bug 极难定位,因为有时还能“凑巧”跑通
示例(简化版):
class Buffer {
int* data_;
size_t size_;
public:
Buffer(const Buffer& other) : size_(other.size_) {
data_ = new int[size_]; // 关键:新内存
std::copy(other.data_, other.data_ + size_, data_);
}
};拷贝构造函数和 operator= 必须成对出现,否则删一半留一半
只重载拷贝构造函数却不重载 operator=,或者反过来,会导致对象在初始化和赋值两种场景下行为不一致——前者走你的深拷贝逻辑,后者走默认浅拷贝,结果就是“有时正常,有时崩溃”。
更麻烦的是:operator= 还得处理自赋值(a = a;),否则 delete 后又去读已释放内存。
- 标准写法是“拷贝-交换”(copy-and-swap):先构造临时对象(复用拷贝构造函数),再交换成员,天然规避自赋值和异常安全问题
- 如果不用 swap,手动实现要三步:检查自赋值 →
delete当前资源 → 深拷贝新资源 - 别忘了在析构函数里统一
delete[](对应new[])或delete(对应new),类型必须严格匹配
现代 C++ 更推荐用 RAII 容器替代裸指针,绕过拷贝难题
不是所有深拷贝都非写不可。用 std::vector、std::unique_ptr、std::shared_ptr 替代裸指针后,拷贝行为由容器自身控制,你几乎不用动拷贝构造函数。
比如:
- 用
std::vector<int></int>替代int*+size_t:拷贝自动深拷贝全部元素 - 用
std::unique_ptr<resource></resource>:默认禁止拷贝,只允许移动——如果你本就不该让两个对象共享资源,这就比手写深拷贝更安全 - 用
std::shared_ptr<resource></resource>:拷贝只是增加引用计数,语义是共享而非复制,适合真正需要共享的场景
裸指针深拷贝不是“高级技巧”,而是历史包袱。现在还硬写,大概率是因为没想清楚资源归属,或者在维护老代码。
真正容易被忽略的是:深拷贝的代价可能很高(比如大数组、嵌套对象树),而用户根本没意识到自己正频繁触发它——比如在循环里传值传递一个大对象。这时候,优先考虑 const 引用传参,或明确启用移动语义。











