news 2026/5/1 16:43:23

深拷贝、浅拷贝

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深拷贝、浅拷贝

一、先理解核心概念:拷贝的本质

拷贝的目的是创建一个新对象,使其与原对象的“内容”一致。而深浅拷贝的核心差异,在于对「堆内存资源」的处理方式:

  • 栈内存(如int、char、指针变量本身):所有拷贝都会复制,无深浅之分;
  • 堆内存(如new出来的数组/对象):浅拷贝只复制“指向堆内存的指针”,深拷贝会复制“指针指向的堆内存内容”。

二、浅拷贝(Shallow Copy):默认的“表层拷贝”

1. 定义

浅拷贝是C++编译器默认生成的拷贝行为:仅复制对象的「栈上成员」(包括指针变量的地址值),不复制指针指向的「堆内存资源」。新对象和原对象共享同一块堆内存,相当于“多个管家管同一个房子”。

2. 浅拷贝的问题:资源冲突(必现!)

如果对象包含堆内存资源,浅拷贝会导致两个致命问题:

  • 重复释放:两个对象析构时,都会释放同一块堆内存,触发未定义行为(程序崩溃);
  • 数据篡改:修改新对象的堆数据,原对象的堆数据也会被改(共享资源)。
3. 示例:浅拷贝的崩溃演示

我们用一个「管理动态数组」的类,演示浅拷贝的问题:

#include<iostream>#include<cstring>usingnamespacestd;// 管理动态字符数组的类(含堆内存资源)classStringWrapper{private:char*str;// 指针指向堆内存的字符数组public:// 构造函数:分配堆内存,初始化字符串StringWrapper(constchar*s){cout<<"构造:分配堆内存"<<endl;str=newchar[strlen(s)+1];// 堆内存分配strcpy(str,s);}// 析构函数:释放堆内存~StringWrapper(){cout<<"析构:释放堆内存"<<endl;if(str){delete[]str;// 释放堆数组str=nullptr;}}// 打印字符串(方便测试)voidprint(){cout<<str<<endl;}// 手动修改堆数据(演示共享问题)voidset(constchar*s){strcpy(str,s);}// 【注意】编译器默认生成的浅拷贝构造/赋值运算符:// StringWrapper(const StringWrapper& other) = default;// StringWrapper& operator=(const StringWrapper& other) = default;};intmain(){// 原对象:分配堆内存,存储"hello"StringWrappers1("hello");// 浅拷贝:s2的str指针和s1的str指向同一块堆内存StringWrapper s2=s1;// 问题1:修改s2的堆数据,s1也被篡改(共享资源)s2.set("world");cout<<"s1的值:";s1.print();// 输出world(而非hello)// 问题2:函数结束,s2先析构→释放堆内存;s1析构→重复释放同一块内存→崩溃return0;}
运行结果(崩溃+错误):
构造:分配堆内存 s1的值:world 析构:释放堆内存 析构:释放堆内存 // 随后程序崩溃(double free or corruption,重复释放内存)

三、深拷贝(Deep Copy):独立的“完整拷贝”

1. 定义

深拷贝需要手动实现:不仅复制对象的栈上成员,还会为新对象重新分配一块独立的堆内存,并将原对象堆内存中的内容完整复制过去。新对象和原对象拥有完全独立的堆资源,相当于“复制房子的所有内容,盖一栋新的一模一样的房子”。

2. 深拷贝的实现:遵循“三法则/五法则”

要实现深拷贝,必须手动编写:

  • 拷贝构造函数(创建新对象时);
  • 拷贝赋值运算符(已有对象赋值时);
  • 析构函数(释放堆资源,已有)。
3. 示例:实现深拷贝解决问题

基于上面的类,补充深拷贝的实现(关键修改处标注):

#include<iostream>#include<cstring>usingnamespacestd;classStringWrapper{private:char*str;public:// 构造函数StringWrapper(constchar*s){cout<<"构造:分配堆内存"<<endl;str=newchar[strlen(s)+1];strcpy(str,s);}// 【核心1】深拷贝构造函数StringWrapper(constStringWrapper&other){cout<<"深拷贝构造:分配新堆内存"<<endl;// 步骤1:为新对象分配独立的堆内存str=newchar[strlen(other.str)+1];// 步骤2:复制原对象堆内存的内容(而非指针)strcpy(str,other.str);}// 【核心2】深拷贝赋值运算符(遵循“先释放、再分配、最后复制”)StringWrapper&operator=(constStringWrapper&other){cout<<"深拷贝赋值:释放旧内存,分配新内存"<<endl;// 步骤0:防止自赋值(如s1 = s1)if(this==&other)return*this;// 步骤1:释放当前对象的旧堆内存delete[]str;// 步骤2:分配新堆内存str=newchar[strlen(other.str)+1];// 步骤3:复制原对象的堆内容strcpy(str,other.str);return*this;}// 析构函数~StringWrapper(){cout<<"析构:释放堆内存"<<endl;if(str){delete[]str;str=nullptr;}}voidprint(){cout<<str<<endl;}voidset(constchar*s){strcpy(str,s);}};intmain(){StringWrappers1("hello");// 深拷贝构造:s2有独立的堆内存StringWrapper s2=s1;// 修改s2的堆数据,s1不受影响(资源独立)s2.set("world");cout<<"s1的值:";s1.print();// 输出hellocout<<"s2的值:";s2.print();// 输出world// 赋值运算符测试:深拷贝StringWrappers3("test");s3=s1;// 调用深拷贝赋值cout<<"s3的值:";s3.print();// 输出hello// 析构:三个对象释放各自的堆内存,无冲突return0;}
运行结果(安全无崩溃):
构造:分配堆内存 深拷贝构造:分配新堆内存 s1的值:hello s2的值:world 构造:分配堆内存 深拷贝赋值:释放旧内存,分配新内存 s3的值:hello 析构:释放堆内存 析构:释放堆内存 析构:释放堆内存

四、深拷贝 vs 浅拷贝:核心区别对比

特性浅拷贝深拷贝
拷贝内容仅复制栈上成员(如指针地址)复制栈上成员 + 重新分配堆内存并复制内容
资源归属新对象与原对象共享堆资源新对象与原对象拥有独立堆资源
安全性有堆资源时必出问题(重复释放/篡改)安全,无资源冲突
效率高(仅复制表层数据)稍低(需分配堆内存+复制内容)
实现方式编译器默认生成(=default)手动实现拷贝构造+赋值运算符
适用对象无堆资源的纯栈对象(如Point{x,y})含堆资源的对象(如动态数组、自定义指针)

五、适用场景:什么时候用深/浅拷贝?

1. 浅拷贝的适用场景

只有当对象无堆内存资源时,才适合用浅拷贝(编译器默认生成即可):

  • 纯栈对象:如struct Point { int x; int y; }class Student { string name; int age; }(string内部已实现深拷贝,对外部是浅拷贝);
  • 明确需要共享资源的场景(需配合引用计数):如std::shared_ptr(浅拷贝指针,但通过引用计数避免重复释放)。
2. 深拷贝的适用场景

只要对象包含堆内存资源(自定义指针、动态数组、手动new的对象),必须实现深拷贝:

  • 自定义容器类(如自定义栈、队列,内部有动态数组);
  • 管理系统资源的类(如文件句柄、网络连接的封装类,需独立管理资源);
  • 要求“对象独立”的场景(修改新对象不影响原对象)。

六、补充:C++11后的优化——移动语义(替代深拷贝)

深拷贝的效率问题(分配内存+复制内容),在处理临时对象(如函数返回值)时尤为明显。C++11引入的移动语义(Move Semantics)可以替代深拷贝:

  • 移动构造/赋值:直接“偷走”临时对象的堆资源(转移指针所有权),无需分配新内存,效率和浅拷贝一样高;
  • 实现:编写StringWrapper(StringWrapper&& other)(移动构造)和operator=(StringWrapper&& other)(移动赋值),用std::move转移资源。

示例(简化的移动构造):

// 移动构造函数:转移堆资源,而非复制StringWrapper(StringWrapper&&other)noexcept{cout<<"移动构造:转移堆资源"<<endl;str=other.str;// 直接拿走指针other.str=nullptr;// 原对象放弃资源所有权,避免析构释放}

七、总结(关键点回顾)

  1. 核心区别:深浅拷贝的本质是「是否复制堆内存资源」——浅拷贝复制指针地址(共享资源),深拷贝复制指针指向的内容(独立资源);
  2. 浅拷贝:编译器默认生成,仅适用于无堆资源的纯栈对象,有堆资源时必须禁用(=delete);
  3. 深拷贝:手动实现拷贝构造+赋值运算符,适用于含堆资源的对象,保证资源独立、安全;
  4. 优化:临时对象场景用移动语义替代深拷贝,兼顾安全和效率。

简单来说:只要你的类里有new/malloc,就必须手动实现深拷贝;如果没有,用编译器默认的浅拷贝就够了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/27 8:21:36

NVIDIA官方推荐:TensorRT如何重塑深度学习推理生态

NVIDIA官方推荐&#xff1a;TensorRT如何重塑深度学习推理生态 在自动驾驶汽车每秒处理数百帧图像、智能客服系统同时响应成千上万用户请求的今天&#xff0c;一个关键问题浮出水面&#xff1a;我们训练得越来越深、越来越大的模型&#xff0c;真的能在真实世界“跑得动”吗&a…

作者头像 李华
网站建设 2026/4/30 9:34:05

如何实现微信个人号API接口二次开发

在私域运营中&#xff0c;是否也面临这样的局面&#xff1f;团队每日陷入手动添加好友、重复解答、频繁发圈的事务循环&#xff1b;客户因响应迟缓而流失&#xff0c;增长受制于效率瓶颈。依赖人力的旧模式&#xff0c;已触及增长的天花板。私域的下一步&#xff0c;注定属于系…

作者头像 李华
网站建设 2026/5/1 12:20:11

疫苗接种点智能调度:资源分配最优化方案

疫苗接种点智能调度&#xff1a;资源分配最优化方案 在城市大规模疫苗接种的高峰期&#xff0c;某个社区卫生服务中心门口排起了长队&#xff0c;而几公里外的另一个站点却门可罗雀。医护人员疲惫不堪&#xff0c;群众抱怨等待时间过长&#xff0c;冷链设备闲置或超负荷运转——…

作者头像 李华
网站建设 2026/4/28 20:34:29

虚假信息传播路径追踪:社会治理的AI视角

虚假信息传播路径追踪&#xff1a;社会治理的AI视角 在微博热搜刚被一条“某地突发核泄漏”刷屏的十分钟内&#xff0c;应急管理部门的监控系统已经锁定了信息源头——一个注册于三个月前、粉丝不足百人的营销号。与此同时&#xff0c;平台自动对该账号发布内容进行限流&#x…

作者头像 李华
网站建设 2026/5/1 9:00:46

震惊!合肥热门门头灯箱定制服务机构排名新鲜出炉!

震惊&#xff01;合肥热门门头灯箱定制服务机构排名新鲜出炉&#xff01; 在商业竞争日益激烈的当下&#xff0c;一个吸引人的门头灯箱对于店铺来说至关重要。它不仅是店铺的“脸面”&#xff0c;更是吸引顾客的重要手段。近期&#xff0c;热门门头灯箱定制服务机构排名新鲜出…

作者头像 李华