news 2026/4/23 14:34:11

进阶篇:从手写深拷贝到 std::string 与移动语义(Rule of Five)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
进阶篇:从手写深拷贝到 std::string 与移动语义(Rule of Five)

在上一篇《C++ 浅拷贝 vs 深拷贝:从 0 开始一步一步讲透(Student 示例·含判断方法)》里,我们已经明确了结论:

  • 默认拷贝(编译器生成)对资源类通常是浅拷贝
  • 资源类想安全,就要自己实现深拷贝(或禁止拷贝 / 使用标准库替代)

这篇进阶主要解决三个“工程里一定会遇到”的点:

  1. 为什么拷贝赋值要先 delete 再 new?(自赋值怎么处理?)
  2. 为什么std::string不会出浅拷贝问题?
  3. 移动语义到底解决什么?Rule of Five 是什么?

1)拷贝赋值为什么要先deletenew

先回顾拷贝赋值发生的场景:

Student s1("Tom", 18); Student s2("Jack", 20); s2 = s1; // s2 已经存在

1.1 如果你不先释放旧资源,会发生什么?

s2原本有一块堆内存保存 "Jack"。
如果你直接new一块新内存覆盖name

name = new char[...]; // 覆盖了旧指针

那么旧的那块内存就“丢失了地址”,再也释放不了 →内存泄漏

所以拷贝赋值正确顺序必须是:

先释放旧资源,再申请新资源,再复制内容。

1.2 为什么要做“自赋值保护”?

自赋值:

s1 = s1;

如果你不判断,代码会这样走:

  • delete[] name;把自己资源删了
  • 然后还想从other.name拷贝(其实就是自己已经被删掉的那块内存)
  • 结果就是未定义行为(可能崩,可能乱码)

因此标准写法必须带上:

if (this == &other) return *this;

2)深拷贝赋值的标准写法(可直接套用)

Student& operator=(const Student& other) { if (this == &other) return *this; delete[] name; // 1) 释放旧资源 age = other.age; name = new char[strlen(other.name) + 1]; // 2) 申请新资源 strcpy(name, other.name); // 3) 拷贝内容 return *this; }

记住:拷贝构造不用先 delete(因为对象刚出生没旧资源),
拷贝赋值必须先 delete(因为对象早就有资源了)。

3)为什么std::string不会出浅拷贝问题?

上一节我们用char*是为了把“资源管理”的坑暴露出来。
工程中你更应该写成这样:

#include <string> class Student { public: std::string name; int age; };

然后:

Student s1; s1.name = "Tom"; Student s2 = s1; // 拷贝构造 Student s3; s3 = s1; // 拷贝赋值

不会崩溃,原因很简单:

std::string自己就是一个资源管理类(RAII),它内部已经把:析构 / 拷贝 / 移动 都实现好了。

所以你拷贝Student时,std::string会自动做正确的事情:

  • 该深拷贝时深拷贝
  • 该移动时移动
  • 自动释放资源,不会 double free

结论:能用标准库容器/字符串,就别手写new/delete管字符串。

4)移动语义:它不是“更安全”,而是“更快”

深拷贝是安全的,但可能很贵:大字符串/大数组拷贝成本高。

移动语义解决的是:

避免复制大块数据,改为“转移资源所有权”。

4.1 直觉理解

  • 拷贝:给你“复印一份资料”
  • 移动:把“资料原件”直接交给你,原来的那份清空

因此移动通常:

  • 不分配新内存
  • 不复制大量数据
  • 性能很好

5)Rule of Three vs Rule of Five(一定要记住)

Rule of Three(三法则)

如果你需要自定义这三个之一,通常就要考虑另外两个:

  1. 析构函数~T()
  2. 拷贝构造T(const T&)
  3. 拷贝赋值T& operator=(const T&)

因为这意味着你在“手动管理资源”。

Rule of Five(五法则)

C++11 之后加入了移动语义,所以资源类通常还要考虑:

  1. 移动构造T(T&&)
  2. 移动赋值T& operator=(T&&)

实战经验:大多数时候你用std::string / vector / unique_ptr
就不需要自己写五件套,标准库已经替你做了。

6)工程建议:三种策略怎么选?

当你写一个“资源类”时,通常三种选择:

策略 A:实现深拷贝(像上一篇 Student 那样)

  • 适用:确实需要“复制一份独立资源”
  • 成本:代码多、容易写错

策略 B:禁止拷贝(资源独占)

比如文件句柄、锁、socket 通常不希望被复制:

Student(const Student&) = delete; Student& operator=(const Student&) = delete;

策略 C:用标准库类型替代裸指针(最推荐)

  • std::string替代char*
  • std::vector替代手写动态数组
  • std::unique_ptr管理独占资源

这是最不容易出错、工程最常用的路线。

7)一句话总结(进阶版)

  • 拷贝构造/拷贝赋值只是“什么时候拷贝”
  • 浅/深拷贝才是“怎么拷贝”
  • 资源类拷贝赋值必须:自赋值保护 + 先释放旧资源再申请新资源
  • 工程里优先用std::string / vector / 智能指针,把深拷贝坑交给标准库处理
  • 移动语义解决的是“性能”,让资源转移比复制更高效

C++ 对象拷贝 / 移动 速查表

项目触发时机对象状态是否分配新内存性能成本是否需要手写典型用途
拷贝构造函数T(const T&)创建新对象时用另一个对象初始化新对象刚出生深拷贝时会中等资源类建议手写T b = a;
拷贝赋值运算符operator=已有对象被另一个对象覆盖对象已存在深拷贝时会中等资源类必须手写b = a;
移动构造函数T(T&&)创建新对象时接收临时对象新对象刚出生通常不会很低可选(性能优化)T b = std::move(a);
移动赋值运算符operator=(T&&)已有对象接收临时对象对象已存在通常不会很低可选(性能优化)b = std::move(a);

“人话理解版”

类型本质行为类比
拷贝构造复制资源复印一份资料
拷贝赋值先丢旧资料,再复印新资料覆盖旧文件
移动构造转移资源所有权把原件直接交给你
移动赋值先丢旧资料,再接收原件把旧文件扔掉,接收原件
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 17:41:18

(2026年最新)AI大模型学习路线图详解:从入门到精通,你的完整学习指南!_大模型学习路线

大模型技术已经成为推动人工智能发展的关键力量。无论你是初学者还是有经验的开发者&#xff0c;想要掌握大模型应用&#xff0c;都需要遵循一定的学习路线。 从核心技术解析到模型微调与私有化部署&#xff0c;逐步深入大模型应用的世界。 这份学习路线图详细的介绍了那年每…

作者头像 李华
网站建设 2026/4/18 16:11:01

想让AI更智能?收藏这篇,小白也能学会调用工具的Agent智能体!

本文介绍了AI智能体&#xff08;Agent&#xff09;的概念和实现方式&#xff0c;通过ReAct框架让AI能够主动调用工具解决问题。文章详细讲解了如何使用LangChain4j定义工具、创建Agent接口和实例&#xff0c;并通过实战案例展示了全能助手的多工具协作能力。此外&#xff0c;还…

作者头像 李华
网站建设 2026/4/22 8:22:56

【课程设计/毕业设计】基于SpringBoot框架的食品安全服务系统基于springboot的食品安全监测及风险预警系统的设计与实现【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/22 4:08:44

windows10 pip安装ete4报错

**ete4&#xff08;Environment for Tree Exploration v4&#xff09;**是生物信息学领域核心的Python库&#xff0c;专注于系统发育树&#xff08;进化树&#xff09;的构建、操作、可视化及分析&#xff0c;广泛应用于进化生物学、生态学、流行病学、基因组学等研究场景。win…

作者头像 李华
网站建设 2026/4/22 18:36:58

四层代理-nat模式

术语: 客户端地址:cip+cport; L4代理侦听的地址:vser+vport; 真实服务器侦听的地址:rser+rport; L2以太网帧头:包含源和目的mac。 L3 ip头:包含源ip和目的ip。 L4 tcp头:包含源port和目的port。 四层代理nat模式在负载均衡设备中应用: NAT 模式数据包均会经过L4代理…

作者头像 李华