学完Efficient c++ (28-31)

条款 28:避免返回 handles 指向对象的内部成分

考虑以下Rectangle类:

struct RectData {Point ulhc;Point lrhc;
};class Rectangle {
public:Point& UpperLeft() const { return pData->ulhc; }Point& LowerRight() const { return pData->lrhc; }private:std::shared_ptr<RectData> pData;
};

这段代码看起来没有任何问题,但其实是在做自我矛盾的事情:我们通过const成员函数返回了一个指向成员变量的引用,这使得成员变量可以在外部被修改,而这是违反 logical constness 的原则的。如果它们返回的是指针或迭代器,相同的情况还是会发生,原因也相同。换句话说,你绝对不应该令成员函数返回一个指针指向“访问级别较低”的成员函数(降低了对象的封装性)

改成返回常引用可以避免对成员变量的修改:

const Point& UpperLeft() const { return pData->ulhc; }
const Point& LowerRight() const { return pData->lrhc; }

但是这样依然会带来一个称作 dangling handles(空悬句柄) 的问题,当对象不复存在时,你将无法通过引用获取到返回的数据。

采用最保守的做法,返回一个成员变量的副本:

Point UpperLeft() const { return pData->ulhc; }
Point LowerRight() const { return pData->lrhc; }

避免返回 handles(包括引用、指针、迭代器)指向对象内部。遵循这个条款可增加封装性,使得const成员函数的行为符合常量性,并将发生 “空悬句柄” 的可能性降到最低。

条款 29:为“异常安全”而努力是值得的

当异常被抛出时,带有异常安全性的函数会:

  1. 不泄漏任何资源。
  2. 不允许数据败坏。

异常安全函数提供以下三个保证之一:

基本承诺: 如果异常被抛出,程序内的任何事物仍然保持在有效状态下,没有任何对象或数据结构会因此败坏,所有对象都处于一种内部前后一致的状态,然而程序的真实状态是不可知的,也就是说客户需要额外检查程序处于哪种状态并作出对应的处理。

强烈保证: 如果异常被抛出,程序状态完全不改变,换句话说,如果函数成功,就是完全成功,如果函数失败,程序会回复到“调用函数之前”的状态。

不抛掷(nothrow)保证: 承诺绝不抛出异常,因为程序总是能完成原先承诺的功能。作用于内置类型(int,指针等等)身上的所有操作都提供 nothrow 保证。

原书中实现 nothrow 的方法是throw(),不过这套异常规范在 C++11 中已经被弃用,取而代之的是noexcept关键字:

int DoSomething() noexcept;

注意,使用noexcept并不代表函数绝对不会抛出异常,而是在抛出异常时,将代表出现严重错误,会有意想不到的函数被调用(可以通过set_unexpected设置),接着程序会直接崩溃。

考虑以下PrettyMenuChangeBackground函数:

class PrettyMenu {
public:...void ChangeBackground(std::vector<uint8_t>& imgSrc);...
private:Mutex mutex;        // 互斥锁Image* bgImage;     // 目前的背景图像int imageChanges;   // 背景图像被改变的次数
};void PrettyMenu::ChangeBackground(std::vector<uint8_t>& imgSrc) {lock(&mutex);delete bgImage;++imageChanges;bgImage = new Image(imgSrc);unlock(&mutex);
}

很明显这个函数不满足我们所说的具有异常安全性的任何一个条件,若在函数中抛出异常,mutex会发生资源泄漏,bgImageimageChanges也会发生数据败坏。

通过以对象管理资源,使用智能指针和调换代码顺序,我们能将其变成一个具有强烈保证的异常安全函数:

void PrettyMenu::ChangeBackground(std::vector<uint8_t>& imgSrc) {Lock m1(&mutex);bgImage.reset(std::make_shared<Image>(imgSrc));++imageChanges;
}

另一个常用于提供强烈保证的方法是我们所提到过的 copy and swap,为你打算修改的对象做出一份副本,对副本执行修改,并在所有修改都成功执行后,用一个不会抛出异常的swap方法将原件和副本交换:

struct PMImpl {std::shared_ptr<Image> bgImage;int imageChanges;
};class PrettyMenu {...
private:Mutex mutex;std::shared_ptr<PMImpl> pImpl;
};void PrettyMenu::ChangeBackground(std::vector<uint8_t>& imgSrc) {Lock m1(&mutex);auto pNew = std::make_shared<PMImpl>(*pImpl);    // 获取副本pNew->bgImage.reset(std::make_shared<Image>(imgSrc));++pNew->imageChanges;std::swap(pImpl, pNew);
}

当一个函数调用其它函数时,函数提供的“异常安全保证”通常最高只等于其所调用的各个函数的“异常安全保证”中的最弱者。

强烈保证并非永远都是可实现的,特别是当函数在操控非局部对象时,这时就只能退而求其次选择不那么美好的基本承诺,并将该决定写入文档,让其他人维护时不至于毫无心理准备。

条款 30:透彻了解 inlining 的里里外外

将函数声明为内联一共有两种方法,一种是为其显式指定inline关键字,另一种是直接将成员函数的定义式写在类中,如下所示:

class Person {
public:...int Age() const { return theAge; }  // 隐式声明为 inline...
private:int theAge;
};

inline诞生之初,它被当作是一种对编译器的优化建议,即将“对此函数的每一个调用”都以函数本体替换之。但在编译器的具体实现中,该行为完全被优化等级所控制,与函数是否内联无关。

在现在的 C++ 标准中,inline作为优化建议的含义已经被完全抛弃,取而代之的是“允许函数在不同编译单元中多重定义”(inlining在大多数c++程序中是编译期行为),使得可以在头文件中直接给出函数的实现。

inline函数无法随着程序库的升级而升级,如果func函数是程序库的一个inline函数,客户将func函数编进其程序中,一旦程序设计者改变func函数,所有的func的客户端程序必须重新编译。而如果func函数是一个non-inline函数。一旦它有修改,客户端只需重新连接就好,如果程序库采用动态连接,func函数的改动可以不知不觉地被程序吸纳。

在调试版本的程序中禁止发生inlining。

在 C++17 中,引入了一个新的inline用法,使静态成员变量可以在类中直接定义:

class Person {
public:...
private:static inline int theAge = 0;  // since C++17
};

条款 31:将文件间的编译依存关系降至最低

C++ 坚持将类的实现细节放置于类的定义式中,这就意味着,即使你只改变类的实现而不改变类的接口,在构建程序时依然需要重新编译。这个问题的根源出在编译器必须在编译期间知道对象的大小,如果看不到类的定义式,就没有办法为对象分配内存。也就是说,C++ 并没有把“将接口从实现中分离”这件事做得很好。

用“声明的依存性”替换“定义的依存性”:

我们可以玩一个“将对象实现细目隐藏于一个指针背后”的游戏,称作 pimpl idiom(pimpl 是 pointer to implemention 的缩写):将原来的一个类分割为两个类,一个只提供接口,另一个负责实现该接口,称作句柄类(handle class)

// person.hpp 负责声明类class PersonImpl;class Person {
public:Person();void Print();...
private:std::shared_ptr<PersonImpl> pImpl;
};// person.cpp 负责实现类class PersonImpl {
public:int data{ 0 };
};Person::Person() {pImpl = std::make_shared<PersonImpl>();
}void Person::Print() {std::cout << pImpl->data;
}

这样,假如我们要修改Person的private成员,就只需要修改PersonImpl中的内容,而PersonImpl的具体实现是被隐藏起来的,对它的任何修改都不会使得Person客户端重新编译,真正实现了“类的接口和实现分离”。

如果使用对象引用或对象指针可以完成任务,就不要使用对象本身:

你可以只靠一个类型声明式就定义出指向该类型的引用和指针;但如果定义某类型的对象,就需要用到该类型的定义式。

如果能够,尽量以类声明式替换类定义式:

当你在声明一个函数而它用到某个类时,你不需要该类的定义;但当你触及到该函数的定义式后,就必须也知道类的定义:

class Date;                     // 类的声明式
Date Today();
void ClearAppointments(Date d); // 此处并不需要得知类的定义

为声明式和定义式提供不同的头文件:

为了避免频繁地添加声明,我们应该为所有要用的类声明提供一个头文件,这种做法对 template 也适用:

#include "datefwd.h"            // 这个头文件内声明 class Date
Date Today();
void ClearAppointments(Date d);

此处的头文件命名方式"datefwd.h"取自标准库中的<iosfwd>

上面我们讲述了接口与实现分离的其中一个方法——提供句柄类,另一个方法就是将句柄类定义为抽象基类,称作接口类(interface class)(它通常不带成员变量,也没有构造函数,只有一个virtual析构函数和一组pure virtual函数,用来描述整个接口)

class Person {
public:virtual ~Person() {}virtual void Print() = 0;...
};

为了将Person对象实际创建出来,我们一般采用工厂模式。可以尝试在类中塞入一个静态成员函数Create用于创建对象:

class Person {
public:...static std::shared_ptr<Person> Create();...
};

但此时Create函数还无法使用,需要在派生类中给出Person类中的函数的具体实现:

class RealPerson : public Person {
public:RealPerson(...) { ... }virtual ~RealPerson() {}void Print() override { ... }private:int data{ 0 };
};

完成Create函数的定义:

static std::shared_ptr<Person> Person::Create() {return std::make_shared<RealPerson>();
}

毫无疑问的是,句柄类和接口类都需要额外的开销:句柄类需要通过 pimpl 取得对象数据,增加一层间接访问、指针大小和动态分配内存带来的开销;而接口类会增加存储虚表指针和实现虚函数跳转带来的开销。

而当这些开销过于重大以至于类之间的耦合度在相比之下不成为关键时,就以具象类(concrete class)替换句柄类和接口类。

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

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

相关文章

使用huggingface实现ALGN进行图像-文本相似性匹配

目录 引言 使用范例 AlignConfig 参数详解 AlignTextConfig 参数详解 示例 AlignVisionConfig 参数详解 示例 AlignProcessor 参数 主要方法 AlignModel 参数 方法 示例 AlignTextModel 参数 前向传播方法 forward 返回值 示例代码 AlignVisionModel 参…

Linux的进程调度实现

经常被问到进程的调度算法有哪些&#xff0c;什么先进先出、短进程优先、时间片轮转、多级反馈多列等等算法能说一大堆&#xff1f;那具体的&#xff0c;linux内核使用了什么样的算法&#xff0c;且来探究一下。 本文所引用源码基于linux内核2.6.34版本。 目录 调度器类 从 s…

探索 PostgreSQL 的高级数据类型 - 第 1 部分

数组和枚举 PostgreSQL 因其可扩展性和多功能性而备受欢迎&#xff0c;除了传统的整数和字符串之外&#xff0c;它还提供了多种数据类型。其中&#xff0c;包括数组和枚举&#xff0c;其为开发者提供了高级的数据建模能力。本文中&#xff0c;我们将深入研究这些复杂的数据类型…

Unity中PICO实现 隔空取物 和 接触抓取物体

文章目录 前言一、隔空取物1、XR Grab Interactable2、调节扔出去时的相关系数3、用手柄射线指向需要抓取的物体后&#xff0c;按下侧边扳机键即可抓取 二、接触抓取物体1、替换手柄上抓取物体的脚本2、在手柄上添加 接触抓取物体的脚本3、在手柄上添加碰撞盒触发器4、在需要抓…

PHAMB: 病毒数据分箱

Genome binning of viral entities from bulk metagenomics data | Nature Communications 安装 ### New dependencies *Recommended* conda install -c conda-forge mamba mamba create -n phamb python3.9 conda activate phamb mamba install -c conda-forge -c biocond…

Java面试题(Guide)

Java 基础 Java 中的几种基本数据类型是什么&#xff1f;对应的包装类型是什么&#xff1f;各自占用多少字节呢&#xff1f; String 、 StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的? String s1 new String("abc");这段代码创建了几个字…

排序算法——快速排序详细解释

快速排序&#xff08;Quicksort&#xff09;是一种常用的排序算法&#xff0c;其基本思想是通过分治的策略将一个数组分成两个子数组&#xff0c;然后分别对这两个子数组进行递归排序 一、快速排序算法的大致思路如下&#xff1a; 1、我们在对列表进行排序的过程中&#xff0c…

IOS降级后从高版本到低版本恢复备份

IOS降级后从高版本到低版本恢复备份 此方法只适用于小版本还原&#xff0c;比如17.4->17.3&#xff0c;未验证大版本恢复可行性手机型号&#xff1a;iphone 13pro 系统版本&#xff1a;17.4 降级版本&#xff1a;17.3.1 步骤 通过itunes或者MacOS系统下对当前版本进行备份…

基于ThinkPHP框架的校园一卡通系统设计与实现

目 录 摘 要 I Abstract II 引 言 1 1 相关技术 3 1.1 框架技术 3 1.1.1 Bootstrap 3 1.1.2 ThinkPHP框架 3 1.2 前端技术 4 1.2.1 JavaScript 4 1.2.2 ECharts 4 1.3 B/S架构 4 1.4 数据库技术 5 1.4.1 MySQL 5 1.5 本章小结 6 2 系统分析 7 2.1 功能需求分析 7 2.2 非功能需…

202441读书笔记|《笠翁对韵》—— 金菡萏,玉芙蓉,酒晕微酡琼杏颊,香尘浅印玉莲双

202441读书笔记|《笠翁对韵》——金菡萏&#xff0c;玉芙蓉&#xff0c;酒晕微酡琼杏颊&#xff0c;香尘浅印玉莲双 《作家榜名著&#xff1a;笠翁对韵》作者李渔&#xff0c;霍俊明。是所有词句都有注音的一本书&#xff0c;轻松学不认识的字&#xff0c;非常朗朗上口的对偶词…

PromptBreeder---针对特定领域演化和发展提示词的方法

原文地址&#xff1a;promptbreeder-evolves-adapts-prompts-for-a-given-domain 论文地址&#xff1a;https://arxiv.org/pdf/2309.16797.pdf 2023 年 10 月 6 日 提示方法分为两大类 硬提示是由人工精心设计的文本提示&#xff0c;包含离散的输入令牌&#xff1b;其缺点…

【Linux】gcc与make、makefile

文章目录 1 gcc/g1.1 预处理1.2 编译1.3 汇编1.4 链接1.4.1 静态链接1.4.2 动态链接 2 make和makefile2.1 依赖关系2.2 依赖方法2.3 伪目标 3 总结 1 gcc/g 当我们创建一个文件&#xff0c;并向里面写入代码&#xff0c;此时&#xff0c;我们该如何使我们的代码能够运行起来呢&…

html--心花怒放

代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><title>Canvas 绘制一个❤</title><link rel"shortcut icon" href"../../assets/images/icon/favicon.ico" type"ima…

c++ primer中文版第五版作业第十五章

仓库地址 文章目录 15.115.215.315.415.515.615.715.815.915.1015.1115.1215.1315.1415.1515.1615.1715.1815.1915.2015.2115.2215.2315.2415.2515.2615.2615.2815.2915.3015.3115.3215.3315.3415.3515.3615.3715.3815.3915.4015.4115.42 15.1 基类希望其派生类进行覆盖的函数&…

C#实现快速排序算法

C#实现快速排序算法 以下是C#中的快速排序算法实现示例&#xff1a; using System;class QuickSort {// 快速排序入口函数public static void Sort(int[] array){QuickSortRecursive(array, 0, array.Length - 1);}// 递归函数实现快速排序private static void QuickSortRecu…

springboot配置Redis缓存多节点

随着微服务架构的广泛应用&#xff0c;分布式缓存系统在提升系统性能、降低数据库压力方面起着关键作用。Redis作为高性能的内存键值对数据库&#xff0c;凭借其丰富的数据结构和高速读写能力&#xff0c;在众多缓存解决方案中脱颖而出。在Spring Boot框架中&#xff0c;我们可…

Python 潮流周刊第 41 期(摘要),赠书5本

本周刊由 Python猫 出品&#xff0c;精心筛选国内外的 250 信息源&#xff0c;为你挑选最值得分享的文章、教程、开源项目、软件工具、播客和视频、热门话题等内容。愿景&#xff1a;帮助所有读者精进 Python 技术&#xff0c;并增长职业和副业的收入。 周刊全文&#xff1a;h…

测试开发面试题|面试真题

📋 个人简介 作者简介:大家好,我是凝小飞,软件测试领域作者支持我:点赞👍+收藏⭐️+留言📝测试开发的面试题,一般会考一些基础的算法编程题,难度不是特别高,除非是大厂的那种卷,如下是我作为面试官整理的一些参考测试题,供参考 算法排序类 Python: 一、list…

ubuntu自带屏幕截图功能

目录 简介开始截屏步骤1.打开截屏软件2.选择区域3.截图 快捷键 录屏方法11.开始录屏2.停止录屏 方法2 补充说明 简介 试了好多开源跨平台截图软件&#xff0c;但是在ubuntu上都或多或少存在问题。ubuntu有自带的截图软件。打算把ubuntu自带的截图软件用起来。 顺便说一下我使…

B端系统升级,登录页必在升级之列,不容置疑。

进行B端界面升级时&#xff0c;首先升级登录页有以下几个原因&#xff1a; 用户体验&#xff1a;登录页是用户进入系统的第一个页面&#xff0c;用户首先接触到的界面。通过升级登录页&#xff0c;可以提升用户的第一印象&#xff0c;增强用户对系统的信任感和好感度&#xff…