EffectiveC++ 55个改善编程与设计的有效方法(含代码分析,一定收藏)

Effective C++ 是一本指导C++程序员编程实践的书籍,涵盖了C++语言联邦、构造/析构/赋值运算、资源管理、设计与声明等多个方面。书中强调了视C++为多范型语言、使用const、避免#define、智能指针管理资源、正确处理构造函数和析构函数、理解编译器默认生成的函数、避免异常传播、使用RAII等原则。此外,还涉及模板与泛型编程,强调了接口设计的重要性、资源管理的正确方式、模板的编译期多态和隐式接口等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

EffectiveC++





简介

effectiveC++ 这本书是C++程序员的工作必备之书,讲述了在C++开发中常用的一些,以及经常注意的一些规则,遵循它且不要忽视它,我们就能写出很好的友善的C++代码。



在这里插入图片描述



项目链接

点击下载: github项目链接


一. 让自己习惯C++


条款01: 视C++为一个语言联邦

C++是一个多重范型编程语言(Multiparadigm programing language)

  1. 支持过程形式
  2. 面向对象形式
  3. 函数形式
  4. 泛型形式
  5. 元编程形式

我们理解其C++时应该视其为 一个相关语言组成的联邦(有4个次语言)

  1. C:对于C++问题的解法类似C的高级解法时,高效的解法就是去映射C语言的规范,不要掺杂过多C++其他此语言特性
  2. Object-Oriented C++: 这部分即 C With Class,C++的主流编程
  3. Template C++: 十分强大的模板编程范式
  4. STL 模板库: STL 对于容器迭代器算法函数对象等等的规约有极佳的紧密配合与协调,伙同STL进行开发,要遵循其规约

C++对于该4个次语言都有它自己的规约, 记住这4个次语言你就会发现C++容易了解的多

  • 建议: C++高校编程守则视情况而变化,取决于你使用C++的哪一个部分。


条款02: 尽量以 const, enum,inline 替换 #define

请看以下代码:

#define ASPECT_RATIO 1.653
#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

ASPECT_RATIO 从未被编译器看到,而是被预处理器展开, 可能出现的问题:

  1. 运用该宏定义的常量时出现了编译错误信息时,错误提示提到的是 1.653 而非常量名,排查错误难
  2. 普通的变量会被编译器看到,会记录到记号表中(symbol table) ,而宏定义的常量不会被记录,导致出现
    常量值的目标码(object code),从而可能造成代码膨胀(代码膨胀会导致额外的换页行为,降低指令高速缓存装置的击中率,带来效率损失)。
  3. 函数宏虽不会有函数调用的栈方面的开销,但是缺点很明显,难读,容易出错

一. 以 const 替换 宏定义常量
在这里插入图片描述

二. 以enum hack 替换 宏定义常量
在这里插入图片描述

三. 以 inline 替换 宏定义函数

在这里插入图片描述

总结

  1. 对于单纯常量, 最好以 const 对象或 enums 替换 #defines
  2. 对于形似函数的宏, 最好改用 inline函数替换 #define


条款03: 尽可能使用 const

一. const 是C++中的对于变量语义约束(不可修改),编译器会强制实行这项约束,只要该值不可被改变(事实),就应该去进行约束

在这里插入图片描述
二. const 最具威力的是面对函数声明时的应用, const可以与函数返回值,各参数,函数自身(成员函数)产生关联

  • 令函数返回一个常量值,可以降低因客户操作而造成的(意外错误)
    在这里插入图片描述
  • const 实施成员函数: 确认该成员函数可作用于 const 对象身上。
    第一:使得 class接口更加容易被理解,(得知那些函数可以改动对象内容那些不行)
    第二:使得操作 const 对象成为可能, (pass by reference-to-const 方式传递对象),我们有const成员函数处理const对象。

成员函数如果只是常量性不同,可以被重载
在这里插入图片描述

如何实施对对象进行const限制的措施:

  1. bitwise const阵营:不更改对象内的任何一个 bit (太过于强硬)
  2. bitwise constness阵营: 对对象的成员变量实施 const(编译器的做法)
  3. logical constness(重要): 一个const成员函数可以修改它所处理的对象的某些 bits,但请确保客户端侦测不出 (实现办法是利用C++的一个与const相关的摆动场: mutable)

在这里插入图片描述

在const 和 non-const 成员函数中避免重复(写出重复的代码)
在这里插入图片描述

总结:

  1. 将某些东西声明为const能帮助编译器去甄别错误用法
    (作用域对象,函数参数,函数返回类型,成员函数本体)
  2. 编译器强制实施 bitwise constness,但我们编写程序时应该使用 “概念上的常量性”
  3. 当const 和 non-const成员函数有着实质等价的实现时,令 non-const 版本调用const版本可避免代码重复


条款04: 确定对象被使用前已经被初始化

一. C++的对象成员变量的初始化发生在进入构造函数本体之前

在这里插入图片描述

如上代码会对成员先进行默认构造函数的调用,之后在进行赋值。

解决办法是: 使用 member initalization list 初始化列表替换赋值动作。 为了规范统一:将全部成员(无物也要使用初始化列表初始化)

在这里插入图片描述
注意:C++类对象的初始化次序: base class 总是早于其 derived class 被初始化, class 的成员变量总是以其声明次序被初始化

二. C++对于定义在不同编译单元内的non-local static 对象的初始化相对次序无明确定义。

在这里插入图片描述

解决办法就是使用 Singleton 使得 non-local static 搬到自己的专属函数中
C++保证, 函数内的 local static对象会在 “首次遇上该对象之定义上” 被初始化。

多线程环境下执行顺序的麻烦性: 尽量在单线程运行期按一定顺序初始化这些 static

在这里插入图片描述

总结:

  1. 为内置型对象进行手工初始化,因为 C++不保证初始化他们
  2. 构造函数最好使用成员初始值,而不要在构造函数本体内使用赋值操作, 且初始
    列列出的成员变量,其排序次序应该和他们在 class 中声明次序相同
  3. 为免除“跨编译单元之初始化次序”问题, 请以 local static 对象替换 non-static 对象


二. 构造/析构/赋值运算


条款05: 了解C++默认调用哪些函数

一. 检阅一个 empty 类编译器为其做的事情

在这里插入图片描述

二: 编译器合成的函数做的事情

在这里插入图片描述

发现: operator= 与 copy构造函数 都是编译器合成, 内置类型的成员使用拷贝bit方式,非内置则调用 其定义的 operator= 与 copy构造函数从右侧操作数拷贝数据

三. 注意点

  1. 默认拷贝赋值运算符/拷贝构造函数在成员含引用类型时不能被生成
  2. 当基类将拷贝赋值运算符/拷贝构造函数声明为 private或 delete, 也是不能被生成的。

在这里插入图片描述
在这里插入图片描述

总结

编译器可以暗自为class 创建default构造函数,copy构造函数,copy assignment 操作符,以及析构函数。


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

一. 最简单的拒绝(copy构造函数与 copy赋值运算符)办法是 声明其为 private

  1. 为了解决在成员函数和友元函数仍然还是能调用,声明其而不定义其是个好办法(会报链接错误)
  2. 为了将链接错误提前到编译期,需要将其继承一个阻止 copy 的 base class(利用了继承了基类的拷贝操作为private的类,编译器自身将不会生成其 拷贝操作,使用时就会报错)

在这里插入图片描述


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

一:问题浮现: 销毁一个heap分配的基类指针(指向的是派生类)内存泄漏问题
在这里插入图片描述
原因: 通过GetTimeKeeper 返回的指针是一个基类指针,销毁基类指针则会取基类的部分(调用基类的析构函数)

官方: C++明白指出,当derived class对象经由一个base class指针被删除,而其base class带一个 non-virtual函数, 其结果就是未定义-实际执行下来发生的就是对象的 derived 成分没被销毁

解决: 给base class 设置一个 virtual 析构函数即可

在这里插入图片描述

二: 验证: 任何 class 带有virtual函数都几乎确定应该有一个 virtual 析构函数, 没有理由地把所有 class 的析构函数设置为 virtual的行为是错误的。

在这里插入图片描述

三: 利用析构函数实现抽象类, 适用于没有其余能定义pure virtual函数的类

在这里插入图片描述

总结

  1. polymorphic base classes 应该声明一个virtual析构函数, 如果 class 带有任何 virtual 函数,他就应该拥有一个 virtual 析构函数

  2. Class 的设计目的如果不是当作 base classes 使用,就不应该声明 virtual 析构函数


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

首先C++并不禁止析构函数抛出异常,但在析构函数中抛出异常很容易导致内存泄漏(程序过早结束)

一: 验证析构函数抛出异常的问题

在这里插入图片描述
在这里插入图片描述

二: 使用最佳策略解决该问题,避免析构函数传播异常

我们要对 “导致 close 抛出异常” 的情况做出反应

重新设计 DBConn接口,使客户对有机会对可能出现的问题作出反应

1: 管理类提供一个 close 函数,赋予客户一个机会处理因该操作而发生的异常。
2: 管理类设置标志位并在析构函数调用时检测其是否正常关闭,如果未关闭,则正常关闭
3: 第二步在析构函数种再次关闭失败后,我们将又退回 “强迫结束程序或吞下异常的套路”

在这里插入图片描述

总结:

  1. 析构函数绝不要吐出异常, 如果一个被析构函数调用的函数可能抛出异常,析构函数
    应该捕捉任何异常, 然后吞下他们(不传播)或结束程序。

  2. 如果客户端需要对某个操作函数运行期间抛出的异常做出反应,那么 class 应该提供一个 普通函数(而非在析构函数种)执行该操作


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

一: 证明在 base class 构造期间, virtual 函数不是 virtual 函数

在这里插入图片描述
原因1: 如果在构造base class时调用的是 derived class 的函数(会使用到derived class成员, 但此时成员都是未构造的,会出现问题)。

原因2: 在derived class对象的 base class构造期间,对象本身是base class而不是 derived class不止virtual函数会被编译器解析至 base class,运行期类型信息,也会把对象视为 base class 。

相对应析构函数执行到base class部分,编译器也会视当前对象为 base class

二:如何确保每一次有 Transaction继承体系上的对象被创建,就会有适当版本的 logTransaction被调用

由于你无法在 base class构造时期通过 virtual函数 调用到 derived class 的函数,
因此可以使用 非virtual 通过在 derived class构造函数传(必要参数)传递到 base class 的构造函数, 进而调用 base class 的通过必要参数而实行的普通函数

在这里插入图片描述

总结

  1. 在base class 构造和析构期间不要调用 virtual 函数,因为这类调用从不下降至 derived class
    (比起当前执行构造函数和析构函数那层),virtual本质上并没有用

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

这份协议可以说是为了实现连锁赋值而 创造的协议

在这里插入图片描述

总结:

连锁赋值几乎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值