当前位置: 首页 > news >正文

【读书笔记】【Effective C++】构造/析构/赋值运算

条款 05:了解 C++ 默默编写并调用了哪些函数

  • 如果程序员自己没有声明,编译器会为 class 创建默认构造函数、拷贝构造函数、拷贝赋值操作符和析构函数。
    • 唯有当这些函数被需要(即被调用)的时候,它们才会被编译器创建出来。
    • 所有这些函数都是 public 且 inline 的。(如果是在继承类中生成的,权限会与基类的对应函数保持一致)
    • 生成的拷贝构造函数和拷贝赋值操作符只是单纯的 bit-wise copy。
  • 如果程序员定义了一个有参构造而没有定义默认构造,则编译器不会自动生成默认构造。(也就是不会抢夺要求参数构造函数的优先级)
  • 如果打算在一个内含 reference 成员的 class 或是含有 const 成员的 class 支持赋值操作时,必须自己定义 copy assignment 操作符。
    • 因为 C++ 不允许让 reference 指向不同对象以及不允许更改 const 成员。
    • 即如果类内有引用变量,默认构造函数和赋值操作符都无效。
  • 如果某个 base class 的 copy assignment 操作符声明为 private,编译器不会为其 derived classes 生成一个 copy assignment 操作符。
    • 因为 derived 类如果能生成赋值操作符,那么语义上应该是可以调用到基类中的拷贝赋值操作符的,所以干脆不生成即可。

条款 06:若不想使用编译器自动生成的函数,就该明确拒绝

  • 这条款中描述了一个场景:希望定义的类是不可复制的,每一个实例都是独一无二的。
    • 通常情况下,不需要复制功能,不声明就可以,但是前一条款说了,拷贝函数会自动生成,且是 public 的。
  • 所以如果想阻止编译器自动生成拷贝函数,有以下两种方法:
    1. 将成员函数声明为 private 而且故意不实现它,这得以阻止使用者调用它。【在这种情况下,使用者调用拷贝函数会产生一个连接期错误】
    2. 可以想办法将前一种方法中的连接期错误转移至编译期,思路也很简单:
      • 仍然是将两个拷贝函数声明为 private,但不是在类本身,而是在一个专门为了阻止拷贝行为而设计的 base 类(空类)中;程序员自己设计的类只需要继承这个 base 类即可。
      • 但这种方案可能会被其他因素影响,因为这个 base 类是空类,多重继承可能会阻止空基类的优化;还有其他微妙的结合,比如 public 继承、虚析构等等。
      • 但总等来说该方案是可行的。

条款 07:为多态基类声明 virtual 析构函数

  • 虚函数的作用主要体现在当用表面上是基类指针,实际上是指向派生类对象进行操作的时候,在这种情况下,当我们销毁对象时,如果不是虚函数,就会造成基类部分被释放,派生类部分不被释放的局部销毁情况。
    • 将函数声明成 virtual 表示它在不同的 derived 类中有不同的实现码。
  • 带有多态性质的基类应该声明虚析构函数:如果一个类中有一个以上的虚函数,则应该声明虚析构函数。
    • 多态可以理解成多种实现方式,在这样的前提下才说应该声明虚析构。
    • STL 容器都没有 virtual 析构函数,所以最好不要继承他们,否则可能会造成内存泄漏。
  • 类不是作为基类用,或不具备多态性,就不应该声明虚析构函数。
  • 纯虚函数将导致 class 抽象,即不能实体化。
    • 抽象类一般都作为 base class 而存在,所以在抽象类中声明一个纯虚析构函数,接着还要在类外为这个纯虚析构函数提供一份定义(什么也不做)。
    • 为纯虚函数提供定义的原因是:当继承类的析构函数运作时候,会调用最深层派生(most derived)的那个类的析构函数,所以没有定义是不可以的。

条款 08:别让异常逃离析构函数

  • 析构函数绝对不要吐出异常,如果一个析构函数调用的函数可能会抛出异常,那么析构函数应该吞掉它们(不传播)或是结束程序。
    • 所提及的两种方法都是通过 try...catch 去捕捉异常并提前结束。
    • 但其实结果都一样,这两种方法无法对抛出异常的情况做出合适的反应。
  • 如果客服需要对某个操作函数运行期间可能抛出的异常做反应,那么 class 应该提供一个普通的成员函数(而不是在析构函数中)执行该操作。
    • 这个普通的成员函数可供用户使用,即在进入析构函数前,用户需要主动调用 close 去进行关闭连接,在这个过程中吐出异常,可以作出应对。【总比在析构函数中吐出异常要好】
    • 但是如果用户不主动调用,进入析构函数后(可以通过一个标志位检测用户有无主动调用),析构函数仍然要走前面 try...catch 的老路。【这无法避免】

条款 09:绝不在构造和析构函数中调用 virtual 函数

  • 基类的构造函数在被调用时派生类还没有创建,所以试图实现这种多态是不可能的,对于析构函数一样,因为派生类对象已经销毁。
    • 要注意的是多个构造函数可能有共同代码,通常会把共同的初始化代码放进一个初始化函数中,此时如果初始化函数包含 virtual 函数,编译器可能难以发现。
    • 所以不仅需要确定构造函数和析构函数中没有调用虚函数,也要保证它们调用的函数也遵从该要求。

条款 10:令 operator= 返回一个 reference to *this

  • 与内置类型与标准库保持一致,使其能实现连锁赋值。(即 x=y=z 的形式)

条款 11:在 operator= 中处理自我赋值

  • 确保对象在进行自我赋值(即传参是自身)时 operator= 有良好的行为,不然很容易会出现错误。【保障自我赋值安全性与异常安全性】
    • 具体的解决方法有:比较来源和目标的地址(例如将传参与 this 指针比较)、正确的语句顺序(例如在复制行为之前,不可以删除原指针)、copy-and-swap(对传参制作一份副本,然后将 this 数据与副本进行交换)。【后两种行为能保障异常安全性,即出了异常,仍然有一个副本保存着原值】

条款 12:复制对象时勿忘其每一个成分

  • 如果为一个 class 添加了新的成员变量,必须同时修改拷贝函数和构造函数。【保证复制了所有 local 成员】
  • 最主要的问题是在继承的场合下:【保证调用了 base 类中适当的拷贝函数】
    • 在有继承关系的类中,如果我们只在拷贝函数(包括拷贝构造和拷贝赋值操作符)中对 derived 类的成员进行了复制,而不显式调用 base 类的拷贝函数时,就会导致部分复制错误。
    • 因为如果不显式调用 base 类的拷贝函数,那么编译器在调用到这个 derived 类的拷贝函数时,会添上 base 类的默认构造函数(即无参构造),这样两个对象中关于 base 类的成员就没有复制下来。
    • 所以在派生类中要显式的加上 base 类的复制函数。
  • 不应该在拷贝赋值操作符中调用拷贝构造函数,反之也同样没有意义。【不要用拷贝函数调用另一个拷贝函数】
    • 如果两者有相似代码,提取到第三个函数供两者调用即可。

相关文章:

  • Vue(四)——全局事件总线, 消息订阅与发布 ,nextTick
  • 【Java】Collection接口迭代器
  • 基于PID的直流电机调速控制系统
  • SSM+基于SSM的课堂考勤管理系统的设计与实现 毕业设计-附源码191617
  • SWUST OJ#99 欧几里得博弈
  • 基于Springboot+mysql的闲置二手交易网站系统设计
  • Code For Better 谷歌开发者之声 ——Tensorflow与深度学习
  • Web阶段一 静态网页
  • 第五章:数组、排序和查找
  • 香橙派 C# IOT .net 引用WiringOP操作引脚高电平低电平 代码实例
  • 高等数学(第七版)同济大学 习题7-7 个人解答
  • Python每日一练(牛客数据分析篇新题库)——第33天:中位函数
  • 10. 元组、集合
  • 清理MySQL中的binlog
  • 计算机毕业设计ssm高校学科竞赛管理系统eolh8系统+程序+源码+lw+远程部署
  • 【编码】-360实习笔试编程题(二)-2016.03.29
  • Android开源项目规范总结
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Java应用性能调优
  • java正则表式的使用
  • jdbc就是这么简单
  • js
  • Mysql5.6主从复制
  • MySQL用户中的%到底包不包括localhost?
  • Shell编程
  • webpack+react项目初体验——记录我的webpack环境配置
  • 翻译--Thinking in React
  • 让你的分享飞起来——极光推出社会化分享组件
  • 入门级的git使用指北
  • 因为阿里,他们成了“杭漂”
  • 做一名精致的JavaScripter 01:JavaScript简介
  • #etcd#安装时出错
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (阿里云万网)-域名注册购买实名流程
  • (笔试题)合法字符串
  • (博弈 sg入门)kiki's game -- hdu -- 2147
  • (第9篇)大数据的的超级应用——数据挖掘-推荐系统
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (剑指Offer)面试题34:丑数
  • (六)激光线扫描-三维重建
  • (四)【Jmeter】 JMeter的界面布局与组件概述
  • (循环依赖问题)学习spring的第九天
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .Net Memory Profiler的使用举例
  • .NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件
  • .NET/C# 避免调试器不小心提前计算本应延迟计算的值
  • .net通用权限框架B/S (三)--MODEL层(2)
  • @javax.ws.rs Webservice注解
  • [caffe(二)]Python加载训练caffe模型并进行测试1
  • [js]- 两个对象的合并(Object.assign)
  • [Linux_IMX6ULL驱动开发]-基础驱动
  • [nginx] 网上最全面nginx教程(近100篇文章整理)
  • [scikit-learn] 第一章 初识scikit-learn及内置数据集介绍
  • [svc]logstash和filebeat之间ssl加密
  • [Thinking in JAVA] 关于内部类的一些知识点