Effective C++(2)

文章目录

  • 2. 构造、析构、赋值运算
    • 条款05:了解C++默默编写并调用哪些函数
    • 条款06:若不想使用编译器自动生成的函数,就该明确拒绝
    • 条款07:为多态基类声明virtual析构函数
    • 条款08:别让异常逃离析构函数
    • 条款09:绝不在构造和析构过程中调用virtual函数
    • 条款10:令 operator= 返回一个指向 *this 的引用
    • 条款11:在operator=中处理“自我赋值”
    • 条款12:复制对象时勿忘其每一个成分


2. 构造、析构、赋值运算

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

如果你自己没有声明,编译器会为他声明一个copy构造函数、一个copy assignment操作符和一个析构函数。
如果你没有声明任何构造函数,编译器也会声明一个default构造函数。

class Empyt{};等价于class Empty{
public:Empty(){...}   //defalut构造函数Empty(const Empty& rhs){...}  //copy构造函数~Empty(){...}   //析构函数Empty& operator=(const Empty& rhs){...}  //copy assignment操作符
};唯有当这些函数被调用,它们才会被编译器创建出来。
Empty e1;                   // 默认构造函数 & 析构函数
Empty e2(e1);               // 拷贝构造函数
e2 = e1;                    // 拷贝赋值运算符

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

原书中使用的做法是将不想使用的函数声明为private,但在 C++11 后我们有了更好的做法

class Uncopyable {
public:Uncopyable(const Uncopyable&) = delete;Uncopyable& operator=(const Uncopyable&) = delete;
};

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

当派生类对象经由一个基类指针被删除,而该基类指针带着一个非虚析构函数,其结果是未定义的,可能会无法完全销毁派生类的成员,造成内存泄漏。消除这个问题的方法就是对基类使用虚析构函数:

class Base {
public:Base();virtual ~Base();
};

如果你不想让一个类成为基类,那么在类中声明虚函数是是一个坏主意,因为额外存储的虚表指针会使类的体积变大。

只要基类的析构函数是虚函数,那么派生类的析构函数不论是否用virtual关键字声明,都自动成为虚析构函数。
虚析构函数的运作方式是,最深层派生的那个类的析构函数最先被调用,然后是其上的基类的析构函数被依次调用。

欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数该被调用。这份信息通常是由一个所谓vptr(virtual table pointer)指针支出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器在其中寻找适当的函数指针。

如果你想将基类作为抽象类使用——也就是不能被实体化的类,但手头上又没有别的虚函数,那么将它的析构函数设为纯虚函数是一个不错的想法。

class Base {
public:virtual ~Base() = 0 {}
};
  • 带有多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数
  • 类的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数

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

在析构函数中吐出异常并不被禁止,但为了程序的可靠性,应当极力避免这种行为。

为了实现 RAII,我们通常会将对象的销毁方法封装在析构函数中,如下例子:

class DBConn {
public:...~DBConn() {db.close();    // 该函数可能会抛出异常}private:DBConnection db;
};

但这样我们就需要在析构函数中完成对异常的处理,以下是几种常见的做法:

  • 杀死程序
DBConn::~DBConn() {try { db.close(); }catch (...) {// 记录运行日志,以便调试std::abort();}
}
  • 吞下因调用close而发生的有异常。不推荐,因为它压制了“某些动作失败”的重要信息。
DBConn::~DBConn() {try { db.close(); }catch (...) {制作运转记录,记下对close的调用失败}
}
  • 重新设计接口,使其客户有机会对可能出现的问题做出反应
class DBConn {
public:...void close() {db.close();closed = true;}~DBConn() {if (!closed) {try {db.close();}catch(...) {// 处理异常}}}private:DBConnection db;bool closed;
};
  • 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下他们或结束程序。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数执行该从左。

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

在创建派生类对象时,基类的构造函数永远会早于派生类的构造函数被调用,而基类的析构函数永远会晚于派生类的析构函数被调用。

在派生类对象的基类构造和析构期间,对象的类型是基类而非派生类,因此此时调用虚函数会被编译器解析至基类的虚函数版本,通常不会得到我们想要的结果。

间接调用虚函数是一个比较难以发现的危险行为,需要尽量避免:

class Transaction {
public:Transaction() { Init(); }virtual void LogTransaction() const = 0;private:void Init(){...LogTransaction();      // 此处间接调用了虚函数!}
};

如果想要基类在构造时就得知派生类的构造信息,推荐的做法是在派生类的构造函数中将必要的信息向上传递给基类的构造函数:

class Transaction {
public:explicit Transaction(const std::string& logInfo);void LogTransaction(const std::string& logInfo) const;...
};Transaction::Transaction(const std::string& logInfo) {LogTransaction(logInfo);                           // 更改为了非虚函数调用
}class BuyTransaction : public Transaction {
public:BuyTransaction(...): Transaction(CreateLogString(...)) { ... }    // 将信息传递给基类构造函数...private:static std::string CreateLogString(...);
}
  • 在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class 。

条款10:令 operator= 返回一个指向 *this 的引用

class Widget {
public:Widget& operator+=(const Widget& rhs) {    // 这个条款适用于...                                    // +=, -=, *= 等等运算符return *this;}Widget& operator=(int rhs) {               // 即使参数类型不是 Widget& 也适用...return *this;}
};

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

“自我赋值”发成在对象赋值给自己时

class Widget{};
Widget w;
w = w;  //赋值给自己a[i] = a[j]; //可能发生自我赋值
*px = *py; //可能发生自我赋值

一些情况下可能会导致意外的错误,例如在复制堆上的资源时:

//若rhs和*this指向的是相同的对象,就会导致访问到已删除的数据。
Widget& operator=(const Widget& rhs){delete pb;pb = new Bitmap(*rhs.pb);return *this;
}

最简单的解决方法是在执行后续语句前先进行证同测试(Identity test):

Widget& operator=(const Widget& rhs){if(this == rhs) return *this;  //证同测试delete pb;pb = new Bitmap(*rhs.pb);return *this;
}

适当安排语句的顺序,就可以做到使整个过程具有异常安全性。例如以下代码,只需要注意在复制pb所指东西之前别删除pb:

Widget& operator=(const Widget& rhs){Bitmap* pOrig = pb;pb = new Bitmap(*rhs.pb);delete pOrig; return *this;
}

还有一种取巧的做法是使用 copy and swap 技术,这种技术聪明地利用了栈空间会自动释放的特性,这样就可以通过析构函数来实现资源的释放:

Widget& operator=(const Widget& rhs) {Widget temp(rhs);std::swap(*this, temp);return *this;
}
  • 确保当前对象自我赋值时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、进行周到的语句顺序、以及copy-and-swap
  • 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

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

这个条款正如其字面意思,当你决定手动实现拷贝构造函数或拷贝赋值运算符时,忘记复制任何一个成员都可能会导致意外的错误。

当使用继承时,继承自基类的成员往往容易忘记在派生类中完成复制,如果你的基类拥有拷贝构造函数和拷贝赋值运算符,应该记得调用它们:

class PriorityCustomer : public Customer {
public:PriorityCustomer(const PriorityCustomer& rhs);PriorityCustomer& operator=(const PriorityCustomer& rhs);...private:int priority;
}PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs): Customer(rhs),                // 调用基类的拷贝构造函数priority(rhs.priority) {...
}PriorityCustomer::PriorityCustomer& operator=(const PriorityCustomer& rhs) {Customer::operator=(rhs);       // 调用基类的拷贝赋值运算符priority = rhs.priority;return *this;
}

不要尝试在拷贝构造函数中调用拷贝赋值运算符,或在拷贝赋值运算符的实现中调用拷贝构造函数。

拷贝构造函数调用拷贝赋值运算符:对正在构造中的对象执行赋值意味着对尚未初始化的对象 执行某些只对初始化的对象有意义的操作。
拷贝赋值运算符调用拷贝构造函数:将试图构造一个已经存在的对象。

  • 拷贝构造函数应该确保复制对象内的所有成员变量及所有base class成分
  • 不要尝试以某个拷贝函数实现另一个。应该将共同机能放进第三个函数中,并由两个拷贝函数共同调用。

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

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

相关文章

微信小程序报错:notifyBLECharacteristicValueChange:fail:nodescriptor的解决办法

文章目录 一、发现问题二、分析问题二、解决问题 一、发现问题 微信小程序报错:notifyBLECharacteristicValueChange:fail:nodescriptor 二、分析问题 这个提示有点问题,应该是该Characteristic的Descriptor有问题,而不能说nodescriptor。 …

web前端之解决img元素组件自有高度的问题

MENU 前言解决办法vertical-align 前言 在HTML和CSS中,img元素默认是行内元素(inline element),类似于文本。由于文本有基线(baseline),所以即使是空白的img元素也会占据一定的高度,以便使基线对齐。 解决办法 要解决这个问题&…

axios如何传递数组作为参数,后端又如何接收呢????

前端的参数是一个数组。 前端编写: 后端接收:

Iterater迭代器和增强for循环

1、Collection接口遍历元素—Iterator迭代器 看一下下面这张图片:可以看出Collection接口有一个父接口Iterable,Iterable接口有一个iterator()方法,iterator()方法的类型是Iterator迭代器,实际上当我们使用方法时,返回…

Go语言的pprof工具是如何使用的?

文章目录 Go语言的pprof工具详解pprof的使用runtime/pprofnet/http/pprof 快速开始获取采样数据通过pprof工具进行性能分析总结 Go语言的pprof工具详解 Go语言作为一个高性能、高并发的编程语言,对性能优化有着极高的要求。在Go语言的标准库中,pprof是一…

linux 安全 iptables防火墙 (一)

Linux包过滤防火墙概述 Linux 系统的防火墙 :IP信息包过滤系统,它实际上由两个组件netfilter 和 iptables组成。 主要工作在网络层,针对IP数据包。体现在对包内的IP地址、端口、协议等信息的处理上。 两大组件 netfilter内核组件 iptables应…

blender安装cats-blender-plugin-0-19-0插件,导入pmx三维模型

UE5系列文章目录 文章目录 UE5系列文章目录前言一、Blender安装二、cats-blender-plugin-0-19-0插件下载三、下载bmp文件四、在blender2.93中安装cats-blender-plugin-0-19-0插件 前言 blender本身不支持pmx三维模型,需要用到cats-blender-plugin-0-19-0插件。 一…

构建全面的无障碍学习环境:科技之光,照亮学习之旅

在信息与科技日益发展的当下,为所有人群提供一个包容和平等的学习环境显得尤为重要,特别是对于盲人朋友而言,无障碍学习环境的构建成为了一项亟待关注与深化的课题。一款名为“蝙蝠避障”的辅助软件,以其创新的设计理念与实用功能…

Offline RL : Context-Former: Stitching via Latent Conditioned Sequence Modeling

paper 基于HIM的离线RL算法,解决基于序列模型的离线强化学习算法缺乏对序列拼接能力。 Intro 文章提出了ContextFormer,旨在解决决策变换器(Decision Transformer, DT)在轨迹拼接(stitching)能力上的不足…

新定义单片机的说明

新定义的官网是https://www.rdsmcu.com/shop/#/,主要经营的是1T系列的51单片机,之前从他们官网上申请了评估板,自己页玩了一段时间,不过玩的不多,特开此专栏记录学习过程,并帮助刚入门的道友快速上手。 我申请的是评估…

DQL(数据查询)

目录 1. DQL概念 2. DQL - 编写顺序 3. 基础查询 3.1 查询多个字段 3.2 字段设置别名 3.3 去除重复记录 3.4 案例 4. 条件查询 4.1 语法 4.2 条件 4.3 案例: 5. 聚合函数 5.1 常见的聚合函数: 5.2 语法 5.3 案例: 6. 分组查…

VScode SSH连接远程服务器报错

一、报错 通过VScode SSH插件远程连接服务器,输入密码后没有连接成功,一直跳出输入密码界面,在输出界面里,一直是Waiting for server log或者是显示Cannot not find minimist 二、处理 🐱: 这个时候应该…

力扣每日一题 5/25

题目: 给你一个下标从 0 开始、长度为 n 的整数数组 nums ,以及整数 indexDifference 和整数 valueDifference 。 你的任务是从范围 [0, n - 1] 内找出 2 个满足下述所有条件的下标 i 和 j : abs(i - j) > indexDifference 且abs(nums…

CTF网络安全大赛web题目:字符?正则?

题目来源于&#xff1a;bugku 题目难度&#xff1a;难 题目描  述: 字符&#xff1f;正则&#xff1f; 题目htmnl源代码&#xff1a; <code><span style"color: #000000"> <span style"color: #0000BB"><?php <br />highl…

C-数据结构-链式存储栈(二次封装)

/* 二次封装 借用已经实现双向链表结构来实现 栈 出栈入栈操作类似于 从头节点开始的插入和删除 */ llist.h #ifndef LLIST_H__ #define LLSIT_H__ #define LLIST_FORWARD 1 #definr LLIST_BACKWARD 2 typedef void llist_op(const void *);//回调函数 typedef int llist_cmp…

分组排序取最大sql理解

分组排序取最大sql理解 --用户过滤&#xff08;只能看到当前用户对应部门用户权限表中的部门&#xff09; select h.pk_tbdept from jygyl_bmyhqxb h left join jygyl_bmyhqxb_b b on h.pk_bmyhqx b.pk_bmyhqx where isnull(h.dr,0) 0 and isnull(b.dr,0) 0 and b.pk…

类图的六大关系

类图中的六大关系包括&#xff1a;继承关系、实现关系、关联关系、聚合关系、组合关系和依赖关系。 1. 继承关系 继承是一种类与类之间的关系&#xff0c;表示一种泛化和特化的关系。子类继承父类的特性和行为。 class Animal {void eat() {System.out.println("This an…

TensorFlow.js

什么是 TensorFlow.js&#xff1f; TensorFlow.js 是一个基于 JavaScript 的机器学习库&#xff0c;它是 Google 开发的 TensorFlow 的 JavaScript 版本。它使得开发者能够在浏览器中直接运行机器学习模型&#xff0c;而不需要依赖于后端服务器或云服务。TensorFlow.js 的主要…

【JavaEE 初阶(十)】JVM

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多进阶知识 目录 1.前言2.JVM内存区域划分3.类加载3.1双亲委派模型 4.垃圾回收&#xff08;GC&#xff0…

【智能优化算法】粒子群优化算法(PSO)【附python实现代码】

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…