深入理解C++三五零法则

news2024/11/24 0:40:36

三五零法则就是三法则(The Rule of Three)、五法则(The Rule of Five)、零法则(The Rule of Zero)。
三五零法则是和C++的特殊成员函数有关,特别是那些涉及对象如何被创建、复制、移动和销毁的函数。这些法则提供了指导原则,帮助开发者设计和实现那些管理资源(如动态内存、文件句柄等)的类。

特殊成员函数

析构函数

~X()

  • 调用每个类成员和基类的析构函数
  • 负责在对象生命周期结束时是否其占用的资源

拷贝构造函数

X(X const& other)

  • 调用每个类成员和基类的拷贝构造函数
  • 通过另一个同类型的现有对象来初始化新对象
  • 自定义对象如何被拷贝,是深拷贝还是浅拷贝

拷贝赋值运算符

X& operator=(X const& other)

  • 调用每个类成员和基类的拷贝赋值运算符
  • 通过另一个同类型的现有对象赋值给自己

移动构造函数

X(X&& other)

  • 调用每个类成员和基类的移动构造函数
  • 通过移动而非拷贝来初始化一个对象,通常涉及资源的转移,使得原对象变为无效状态

移动赋值运算符

X& operator=(X&& other)

  • 调用每个类成员和基类的移动赋值运算符
  • 以赋值的形式将对象资源转移

特殊成员函数中还有一个是默认构造函数,但其不涉及资源的管理,所以和三五零法则没有什么关系,这里就不介绍了。

编译器和特殊成员函数

class X {
public:
	int a = 1;
};

int main()
{
	X a, b;
	a.a = 2;
	b.a = 3;
	a = b;
	std::cout << "a.a = " << a.a << std::endl;

	return 0;
}

上面代码的输出结果是:
a.a = 3
我们思考一下,为什么X类中没有显示定义拷贝赋值运算符,但a = b;能够有效地把b对象的数据赋值给a对象。
其实,在很多时候,自定义类的特殊成员函数的实现几乎是一样的,为了提升语言的易用性,编译器会在满足某些条件下的时候提供默认行为。
下图展示的是编译器隐式定义特殊成员函数的条件:
未命名文件 (8).png

该图引用了Howard Hinnant的演讲稿。

注意
上图只是说明了用户显式定义某函数时,会影响编译器对其他函数的处理。
编译器是否隐式定义某函数,不仅仅取决于用户显式定义了什么,还与类成员对应类型和基类是否支持对应函数有关。比如基类不支持默认构造,那么派生类的默认构造函数会被标记为delete。

三五零法则

三法则

法则内容

三法则规定,如果一个类需要显式定义以下其中一项时,那么它必须显示定义这全部的三项:

  • 拷贝构造函数
  • 拷贝赋值运算符
  • 析构函数

案例说明

根据RAII原则,当类手动管理至少一个动态分配的资源时,通常需要实现上述函数。

class Student {
public:
    Student(char* name, int id) {
        this->id = id;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
    
    ~Student() {
        delete[] this->name;
    }

private:
    int id;
    char* name;
};

在这个示例中,我们有一个Student类手动管理了动态分配的资源(即name),构造函数为name分配内存,析构函数释放分配的内存。
但是当Student的对象被复制时会发生什么?

Student s1("Tom", 12);
Student s2 = s1;

当构造s2时,将执行Student的默认拷贝构造函数(因为用户没有显式定义拷贝构造函数)。默认的拷贝构造函数将每个成员进行浅拷贝,这意味着s1.names2.name都指向同一块内存。
main()函数结束时会发生什么?s2的析构函数将被调用,这将释放name所指向的内存,然后s1的析构函数被调用,它将再次尝试释放name指向的内存,但是这块内存已经被释放了!这就导致重复释放内存。
为了避免这种情况,需要提供适当的复制操作:

// 拷贝构造函数
Student(const Student& other) {
    this->id = other.id;
    this->name = new char[strlen(other.name) + 1];
    strcpy(this->name, other.name);
}

// 拷贝赋值运算符
Student& operator=(const Student& rhs) {
    // 防止自拷贝
    if (this != &rhs) {
        this->id = rhs.id;
    
        // delete old data
        if (this->name) {
          delete[] this->name;
        }
    
        this->name = new char[strlen(rhs.name) + 1];
        strcpy(this->name, rhs.name);
    }

    return *this;
}

拷贝构造函数和拷贝赋值运算符都执行动态分配资源的深拷贝。

五法则

法则内容

五法则是三法则的扩展。五法则规定,如果一个类需要显式定义以下其中一项时,建议显式定义全部的五项:

  • 拷贝构造函数
  • 拷贝赋值运算符
  • 析构函数
  • 移动构造函数
  • 移动赋值运算符

除了三法则中的三项外,我们还建议实现移动语义。与拷贝操作相比,移动操作更加高效,因为它们利用已分配的内存并避免不必要的拷贝操作。
不实现移动语义通常不被视为错误。如果缺少移动语义,编译器通常会尽可能使用效率较低的复制操作。如果一个类不需要移动操作,我们可以轻松跳过这些操作。但是,使用它们会提高效率。

因为用户显式定义三法则中的任意一项时,会阻止编译器隐式定义移动语义,导致失去优化的机会。
该法则只是建议,不做强制要求。

案例说明

我们还是在三法则的案例基础上添加移动语言:

// 移动构造函数
Student(Student&& other) {
    this->id = other.id;
    this->name = other.name;
    other.name = nullptr;
}

// 移动赋值运算符
Student& operator=(Student&& rhs) {
    // 防止自移动
    if (this != &rhs) {
        this->id = rhs.id;
        
        // 删除原数据(防止内存泄漏)
        if (this->name) {
            delete[] this->name;
        }
        
        this->name = rhs.name;
        rhs.name = nullptr;
    }
    return *this;
}

调用代码:

Student s1("John", 10);
Student s2 = s1; // 调用拷贝构造函数
Student s3;
s3 = s1; // 调用拷贝赋值运算符

Student s4("Jane", 12);
Student s5 = std::move(s4); // 调用移动构造函数
Student s6;
s6 = std::move(s5); // 调用移动赋值运算符

使用std::move()可以强制调用移动语义。

零法则

法则内容

如果没有显式定义任何特殊成员函数,则编译器会隐式定义所有特殊成员函数(成员变量也会影响隐式定义)。
零法则就是建议优先选择不需要显式定义特殊成员函数的情况。

简单来说,如果类需要管理动态资源(如动态内存、文件句柄、网络连接等)就需要遵循五法则;如果类不需要管理动态资源,那最好不要显式定义析构函数、拷贝/移动构造函数、拷贝/移动赋值运算符。
如果类的所有成员都遵循零法则,那么整个类也遵循零法则。

零法则说到底就是建议使用智能指针和其他资源管理工具,以自动处理资源的创建和销毁。这样大多数类都无需直接管理资源,从而可以避免许多常见的资源管理错误,如资源泄漏、重复释放等。通过遵循零法则,开发者可以编写更简洁、更安全的代码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1796369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESD防护SP3232E真+3.0V至+5.5V RS-232收发器

特征 采用3.0V至5.5V电源&#xff0c;符合真正的EIA/TIA-232-F标准 满载时最低 120Kbps 数据速率 1μA 低功耗关断&#xff0c;接收器处于活动状态 &#xff08;SP3222E&#xff09; 可与低至 2.7V 电源的 RS-232 互操作 增强的ESD规格&#xff1a; 15kV人体模型 15kV IEC1000…

Java Web学习笔记17——Vue快速入门

什么是Vue&#xff1f; Vue是一套前端框架&#xff0c;免除原生JavaScript中的DOM操作&#xff0c;简化书写。 基于MVVM&#xff08;Model-View-ViewModel&#xff09;思想&#xff0c;实现数据的双向绑定&#xff0c;将编程的关注点放在数据上。 官网&#xff1a;https://v…

概率分析和随机算法

目录 雇佣问题 概率分析 随机算法 生日悖论 随机算法 概率分析 球与箱子 总结 雇佣问题 有n个候选人面试&#xff0c;如果面试者比目前雇佣者的分数高&#xff0c;评价更好&#xff0c;那么就辞掉当前雇佣者&#xff0c;而去聘用面试者&#xff0c;否则继续面试新的候…

区块链简要介绍及运用的技术

一、区块链的由来 区块链概念最早是从比特币衍生出来的。 比特币&#xff08;Bitcoin&#xff09;诞生于2008年&#xff0c;是由一个名叫中本聪&#xff08;Satoshi Nakamoto&#xff09;的人首次提出&#xff0c;这个人非常神秘&#xff0c;至今没有他的任何准确信息。在提出…

三、【源码】Mapper XML的解析和注册使用

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/03-parse-mapperXML Mapper XML的解析和注册使用 流程&#xff1a; 1.Resources加载MyBatis配置文件生成Reader字符流 2.SqlSessionFact…

Activity->Activity中动态添加Fragment->add和replace方式添加的区别

XML文件 Activity布局文件R.layout.activity_main <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:id"id/root_ll"android:orientation"v…

一个简单好用的 C# Animation Easing 缓动动画类库

文章目录 1. 类库说明2.使用步骤2.1 创建一个Windows Form 项目2.2 安装类库2.3 编码效果3.代码下载1. 类库说明 App.Animations 类库是一个很精炼、好用的 csharp easing 动画库 基于 net-standard 2.0提供 Fluent API,写代码非常舒服。支持多个参数同时参与动画。自带了十几…

Flutter基础 -- Flutter常用组件

目录 1. 文本组件 Text 1.1 基础用法 1.2 Text 定义 1.3 Text 示例 1.4 Text.rich、RichText 、TextSpan 1.5 RichText 示例 2. 导入资源 2.1 加入资源 2.2 加入图片 3. 图片组件 image 3.1 colorBlendMode 混合参数 3.2 fit 图片大小适配 3.3 ImageProvider 图片…

【Python报错】已解决NameError: name ‘xxx‘ is not defined

解决Python报错&#xff1a;NameError: name ‘xxx’ is not defined 在Python编程中&#xff0c;NameError是一个非常常见的错误类型&#xff0c;它发生在你尝试访问一个未被定义的变量时。本文将介绍这种错误的原因&#xff0c;以及如何通过具体的代码示例来解决这个问题。 …

深度学习笔记: 最详尽LinkedIn Feed 排名系统设计

欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家&#xff01; LinkedIn Feed 排名 1. 问题陈述 设计一个个性化的LinkedIn Feed&#xff0c;以最大化用户的长期参与度…

【MMU】——ARM 一级页表

文章目录 一级页表项即 entry 的格式如下 从上图可以看出 L1 页表项有四种可能类型 产生中止异常的故障条目。这可能是预取或数据中止、取决于访问类型。这实际上表示虚拟地址未映射 bit[1:0] = 00指向 L2 转换表的条目。这样就能将 1MB 的内存分页 bit[1:0] = 01。1MB 段转换…

问题:8255A的端口A工作在方式2时,使用端口C的______作为与CPU和外部设备的联络信号。 #媒体#经验分享#其他

问题&#xff1a;8255A的端口A工作在方式2时&#xff0c;使用端口C的______作为与CPU和外部设备的联络信号。 参考答案如图所示

《互联网政务应用安全管理规定》电子邮件安全如何整改?

继上篇文章&#xff08;解读《互联网政务应用安全管理规定》网络和数据安全中的身份认证和审计合规&#xff09;之后&#xff0c;本篇文章继续解读第五章“电子邮件安全”&#xff0c;为党政机关事业单位提供电子邮件系统整改思路。 “电子邮件安全”内容从第三十一条到第三十…

vscode 离线下载指定版本插件和安装方法

1、背景 由于不同的vscode版本需要安装对应的插件版本&#xff0c;一般情况下&#xff0c;vscode版本会落后于vscode插件库提供的可以下载的插件版本&#xff0c;网页一般只会提供最新的插件下载版本&#xff0c;因此我们需要下载指定的版本需要采取一些措施。 2、获取需要安…

基于Python的实验室管理系统的设计与实现(论文+源码)_kaic

摘 要 随着实验室设备越来越多&#xff0c;实验室及其设备管理工作变得越来越繁重&#xff0c;还存在些管理模式仍旧处于手工管理模式和一些抢占实验室的不文明现象&#xff0c;传统的手工模式已经满足不了日益增长的管理需求&#xff0c;而本系统摒弃传统模式&#xff0c;开启…

移动端 UI 风格,魅力无限

移动端 UI 风格&#xff0c;打造极致体验

【因果推断python】21_匹配2

目录 匹配估计器 匹配估计器 子分类估计器在实践中用得不多&#xff08;我们很快就会明白为什么&#xff0c;主要是因为维度诅咒这个原因&#xff09;&#xff0c;但它让我们很好地、直观地了解了因果推理估计器应该做什么&#xff0c;以及它应该如何控制混淆因素。这使我们能…

输电线路巡视无人机故障坠落防护装置:守护飞行安全的防线

输电线路巡视无人机故障坠落防护装置&#xff1a;守护飞行安全的防线 无人机作为科技发展的证明&#xff0c;应用非常广基本随处可见&#xff0c;人们会用到它拍照、作业、救援、灭火等等&#xff0c;当然它在电力领域同样具备重要用途&#xff0c;为电力巡检、故障排查等任务…

7.高级纹理

前面的基础纹理包括法线纹理、渐变纹理和遮罩纹理等。这些纹理都属于低纬&#xff08;一维或二维&#xff09;纹理。 立方体纹理&#xff08;Cubemap&#xff09;实现环境映射 渲染纹理&#xff08;Render Texture&#xff09; 程序纹理&#xff08;Procedure Texture&#…

yum进阶

yum的主要的作用&#xff1a;依赖关系、自动安装、自动升级 实验一&#xff1a;网页版的形式做一个yum源 主&#xff1a;test1的ip192.168.10.20 作为网页版的yum源 1、下载httpd 开启httpd服务 Apache的服务器默认的访问地址/var/www/html/目录下 2、创建目录centos7 3…