news 2026/7/15 2:42:02

进阶篇:从手写深拷贝到 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/7/15 1:47:19

2025LLM核心架构进展复盘:Attn与MOE研究精选(收藏版)

本文回顾了2025年大模型&#xff08;LLM&#xff09;在注意力机制&#xff08;Attn&#xff09;和专家混合&#xff08;MOE&#xff09;方面的核心架构进展。重点介绍了TPA、MoBA、NSA等创新方法&#xff0c;如低秩压缩KVcache、Top-k block注意力、原生稀疏注意力等&#xff0…

作者头像 李华
网站建设 2026/7/15 2:40:45

导师推荐!AI论文写作软件 千笔·专业论文写作工具 VS speedai 本科生专属

随着人工智能技术的迅猛迭代与普及&#xff0c;AI辅助写作工具已逐步渗透到高校学术写作场景中&#xff0c;成为本科生、研究生完成毕业论文不可或缺的辅助手段。越来越多面临毕业论文压力的学生&#xff0c;开始依赖各类AI工具简化写作流程、提升创作效率。但与此同时&#xf…

作者头像 李华
网站建设 2026/7/1 22:59:56

原理详解:XinServer 是如何做到接口一键生成的?

原理详解&#xff1a;XinServer 是如何做到接口一键生成的&#xff1f; 不知道你有没有过这种经历&#xff1a;产品经理拿着原型图过来&#xff0c;说下周一要上线一个内部管理系统&#xff0c;需要用户管理、部门管理、权限控制&#xff0c;外加一堆业务表单。你心里一咯噔&am…

作者头像 李华
网站建设 2026/7/1 12:50:05

用实力说话千笔·专业降AI率智能体,全网顶尖的降AIGC网站

在AI技术迅速渗透学术写作领域的今天&#xff0c;越来越多的本科生开始借助AI工具提升论文写作效率。然而&#xff0c;随着各大查重系统对AI生成内容的识别能力不断提升&#xff0c;如何有效降低论文中的AIGC率和重复率&#xff0c;已成为困扰无数学生的难题。面对市场上琳琅满…

作者头像 李华