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

三五零法则就是三法则(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()可以强制调用移动语义。

零法则

法则内容

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

苹果不会在WWDC 2024中推出任何搭载M4芯片的Mac电脑

虽然苹果公司已在上月推出了首搭 M4 芯片的 iPad Pro&#xff0c;不过彭博社的马克・古尔曼在最近的实时通讯中透露苹果公司不会在即将进行的 WWDC 2024 开发者大会中推出任何搭载 M4 芯片的 Mac 电脑&#xff08;不会推出任何硬件产品&#xff09;。 此前报道&#xff0c;苹果…

如何自动生成数据库的样本数据(以MySQL和SQLynx为例)

目录 1 功能概述 2 主要特点 3 使用场景 4 使用示例 5 结论 SQLynx 是一款领先的 SQL 集成开发环境&#xff08;IDE&#xff09;&#xff0c;其强大的功能得到了全球用户的广泛认可。SQLynx 不仅在数据库管理和 SQL 查询方面表现出色&#xff0c;还提供了一项特别实用的功能…

【Python报错】已解决AttributeError: ‘method‘ object has no attribute ‘xxx‘

解决Python报错&#xff1a;AttributeError: ‘method’ object has no attribute ‘xxx’ 在Python中&#xff0c;AttributeError通常表明你试图访问的对象没有你请求的属性或方法。如果你遇到了AttributeError: method object has no attribute xxx的错误&#xff0c;这通常意…

宇宙数字宣布2023年上半年盈利翻倍,数字货币挖矿业务持续增长

2023年3月8日宇宙数字公司在2023年上半年盈利翻倍的消息,彰显了该公司在数字货币挖矿领域的卓越表现和领先地位。这一成就是宇宙数字创新研发策略成功的明证,同时也体现了其高效能挖矿产品和解决方案在全球市场的广泛认可和需求。 随着数字货币市场的持续变化和发展,宇宙数字公…

15- Redis 中的 整数集合 数据结构

整数集合是 Set 对象的底层实现之一。当一个 Set 对象只包含整数值元素&#xff0c;并且元素数量不大时&#xff0c;就会使用整数集合这个数据结构作为底层实现。 1. 整数集合结构设计 整数集合本质上是一块连续内存空间&#xff0c;它的结构定义如下&#xff1a; typedef s…

七月份大理站、ACM独立出版、高录用稳检索,2024年云计算与大数据国际学术会议(ICCBD 2024)

【ACM独立出版 | 高录用 | EI核心检索稳定】 2024年云计算与大数据国际学术会议&#xff08;ICCBD 2024) 2024 International Conference on Cloud Computing and Big Data (ICCBD 2024) 一、重要信息 大会官网&#xff1a;www.iccbd.net &#xff08;点击投稿/参会/了解会…

c语言速成系列指针上篇

那么这一篇文章带大家学习一下c语言的指针的概念、使用、以及一些注意事项。 指针的概念 指针也就是内存地址&#xff0c;指针变量是用来存放内存地址的变量。就像其他变量或常量一样&#xff0c;您必须在使用指针存储其他变量地址之前&#xff0c;对其进行声明。 大白话讲解…

【TB作品】MSP430F149 单片机 音乐喷泉

功能 声音越大&#xff0c;亮的灯越多。 oled显示出当前的声音大小。 硬件接线 //OLED----MSP430 //VCC-----3.3V //GND-----GND //D0------P3.2 //D1------P3.0 //RES-----P2.0 //DC------P2.2 //CS------P8.1 led P4八个引脚 adc P6.0 部分代码 _EINT();while (1){adok…

移动端 UI 风格,打造极致体验

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

Python疑难杂症--考试复习

1.排序输出字典中数据 dic1 {Tom:21,Bob:18,Jack:23,Ana:20} dic2 {李雷:21,韩梅梅:18,小明:23,小红:20} nint(input()) if n>len(dic1):nlen(dic1) print(sorted(dic1.keys())[:n]) print(sorted(dic2.items(),keylambda item:item[1])[:n]) 2.罗马数字转换 def F(s):d{…

上位机图像处理和嵌入式模块部署(f407 mcu中的项目开发特点)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和soc相比较&#xff0c;mcu的项目规模一般不算大。因为&#xff0c;soc项目中&#xff0c;从规划、硬件开发、驱动、应用端、服务器端到测试&…

蓝桥杯物联网竞赛 比赛总结

CUBEMX配置建议&#xff1a; 对于CUBEMX配置来说stm32l071kbu6的引脚不算太多&#xff0c;功能模块相对的也不多&#xff0c;所以我建议直接熟练到能将所有模块烂熟于心&#xff0c;不用看原理图就能熟练配置下来&#xff0c;因为国赛看原理图去配置太花费时间 我建议学习的时…

【设计模式深度剖析】【4】【行为型】【策略模式】

文章目录 策略模式定义英文原话直译 角色类图策略接口Strategy&#xff1a;具体策略类上下文类Context测试类 策略模式的应用策略模式的优点策略模式的缺点策略模式的使用场景 策略模式 策略模式&#xff08;Strategy Pattern&#xff09; Strategy策略也称作Policy政策。 想…

Springboot jar运行时,将jar内的文件拷贝到文件系统中

背景 因为执行需要&#xff0c;需要把jar内templates文件夹下的的文件夹及文件加压到宿主机器的某个路径下&#xff0c; 以便执行对应的脚本文件 PS: 通过类加载器等方式&#xff0c;直接getFile遍历文件&#xff0c;在idea中运行是没问题的&#xff0c;但是当打包成jar运行就会…

【JavaEE】留言板与图书管理系统

目录 留言板1. 准备工作2. 约定前后端交互接口lombok3. 服务器代码4. 调整前端页面代码 图书管理系统1. 准备工作2. 约定前后端交互接口3. 服务器代码4. 调整前端页面代码 留言板 需求: 界⾯如下图所⽰ 输⼊留⾔信息, 点击提交. 后端把数据存储起来.⻚⾯展⽰输⼊的表⽩墙的信…

Mysql使用中的性能优化——搭建Mysql的监测服务

大纲 环境安装配置Mysql安装设置root密码新增远程访问账户修改绑定地址重启 新增 MySQL Server Exporter 用户 安装启动mysqld_exporter安装启动新增配置启动 安装启动Prometheus创建用户下载并解压修改配置启动 安装启动grafana安装启动 测试参考资料 抛开场景和数据&#xff…

【YOLOv8改进[CONV]】SPDConv助力YOLOv8目标检测效果 + 含全部代码和详细修改方式 + 手撕结构图

本文将使用SPDConv助力YOLOv8目标检测效果的实践,文中含全部代码、详细修改方式以及手撕结构图。助您轻松理解改进的方法。 改进前和改进后的参数对比: 目录 一 SPDConv 二 SPDConv助力YOLOv8目标检测效果 1 整体修改 ① 添加SPDConv.py文件 ② 修改ultralytics/nn/tas…

笔记95:车辆横向动力学方程转化为误差形式 -- 详细推导过程

1. 非误差型车辆横向动力学方程 注&#xff1a;关于轮胎侧偏刚度的正负 深蓝课程推导得到的车辆横向动力学返程使用的轮胎侧偏刚度是默认为正数&#xff1b;老王课程推导得到的车辆横向动力学方程使用的轮胎侧偏刚度是默认为负数&#xff1b; 1.1 深蓝课程推导得到的方程&…

【Qt知识】部分QWidget属性表格

QWidget是Qt库中所有图形用户界面组件的基类&#xff0c;它提供了大量属性以供自定义和配置控件的行为和外观。下面列出了一些主要的QWidget属性及其作用。 属性 作用 accessibleName 控件的辅助技术名称&#xff0c;用于无障碍访问。 accessibleDescription 控件的辅助技…

18 - 各赛事的用户注册率(高频 SQL 50 题基础版)

18 - 各赛事的用户注册率 -- 注册率注册用户数/所有用户数 selectr.contest_id,round(100*count(*)/(select count(*) from Users),2) percentage from Register r group by r.contest_id order bypercentage desc,r.contest_id ASC;