news 2026/3/3 20:28:29

深拷贝、浅拷贝

作者头像

张小明

前端开发工程师

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/2/28 22:32:23

日志留存策略优化:存储成本与法规遵从平衡

TensorRT 推理优化实战&#xff1a;如何释放 GPU 的极致性能 在自动驾驶系统每秒处理上千帧图像、智能客服要求毫秒级响应的今天&#xff0c;模型推理早已不再是“能跑就行”的阶段。当一个训练好的 PyTorch 模型从实验室走向生产环境时&#xff0c;真正的挑战才刚刚开始——我…

作者头像 李华
网站建设 2026/3/1 23:46:20

NVIDIA TensorRT镜像安装与配置最简教程

NVIDIA TensorRT镜像安装与配置最简教程 在AI模型日益复杂、部署场景愈加多样化的今天&#xff0c;推理性能已经成为决定系统能否落地的关键瓶颈。一个训练得再好的模型&#xff0c;如果在线上服务中响应迟缓、吞吐低下&#xff0c;那它的实际价值将大打折扣。尤其是在自动驾驶…

作者头像 李华
网站建设 2026/3/2 5:28:16

多语言翻译服务质量保障:通信无国界的基石

多语言翻译服务质量保障&#xff1a;通信无国界的基石 在全球化浪潮席卷各行各业的今天&#xff0c;企业跨国协作、科研机构联合攻关、用户跨语言社交已成常态。然而&#xff0c;语言鸿沟依然是信息流通的隐形壁垒。尽管深度学习驱动的神经机器翻译&#xff08;NMT&#xff09;…

作者头像 李华
网站建设 2026/2/22 22:15:20

跨区域数据同步加速:全球化业务的底层支撑

跨区域数据同步加速&#xff1a;全球化业务的底层支撑 在当今全球化的数字生态中&#xff0c;用户对服务响应速度的容忍度正变得越来越低。无论是欧洲消费者在午夜下单购物、东南亚用户与语音助手对话&#xff0c;还是美洲金融机构进行实时反欺诈决策&#xff0c;他们都不希望因…

作者头像 李华
网站建设 2026/2/26 3:24:03

植物养护提醒机器人:阳台绿植不再轻易枯萎

植物养护提醒机器人&#xff1a;阳台绿植不再轻易枯萎 在城市生活的方寸阳台上&#xff0c;一盆绿植往往承载着人们对自然的向往。然而&#xff0c;工作繁忙、出差频繁&#xff0c;常常让人忘记浇水、忽视光照——再顽强的生命也扛不住长期疏于照料。于是&#xff0c;我们开始思…

作者头像 李华