Effective C++ 55个改善编程与设计的有效方法

目录

简介

项目链接

一. 让自己习惯C++

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

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

条款03: 尽可能使用 const

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

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

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

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

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

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

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

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

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

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

三. 资源管理

条款13: 以对象管理资源

条款14: 在资源管理类中小心 copying 行为

条款15: 在资源管理类中提供对原始资源的访问

条款16: 成对使用 new 和 delete 时要采取相同形式

条款17: 以独立语句将 newed 对象置入智能指针

四. 设计与声明

条款18: 让接口容易被正确使用, 不易被误用

条款19: 设计 class 犹如设计 type

条款20: 宁以 pass-by-reference-to-const 替换 pass-by-value

条款21: 必须返回对象时,别妄想返回其 reference

条款22: 将成员变量声明为 private

条款23: 宁以non-member,non-friend 替换 member 函数

条款24: 若所有参数皆需类型转换,请为此采用 non-member 函数

条款25: 考虑写出一个不抛异常的 swap 函数

五. 实现

条款26: 尽可能延后变量定义式的出现时间

条款27: 尽量少做转型动作

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

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

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

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

六. 继承与面向对象设计

条款32: 确定你的 public 继承素模出 is-a 关系

条款33:避免遮掩继承而来的名称

条款34: 区分接口继承和实现继承

条款35: 考虑 virtual 函数以外的其他选择

条款36: 绝不重新定义继承而来的 non-virtual 函数

条款37: 绝不重新定义继承而来的缺省参数值

条款38: 通过复合塑模出 has-a 或 “根据某物实现出”

条款39: 明智而审慎地使用 private 继承

条款40: 明智而审慎地使用多重继承

七. 模板与泛型编程

条款41:了解隐式接口和编译期多态

条款42:了解 typename 的双重意义

条款43: 学习处理模板化基类内的名称

条款44: 将与参数无关的代码抽离 templates

条款45: 运用成员函数模板接受所有兼容类型

条款46: 需要类型转换时请为模板定义非成员函数

条款47: 请使用 traits classes 表现类型信息

条款48: 认识 template 元编程

八. 定制 new 与 delete

条款49: 了解 new-handler 的行为

条款50: 了解 new 和 delete 的合理替换时机

条款51: 编写 new 和 delete 时需固守常规

条款52: 写了 placement new 也要写 placement delete

九. 杂项讨论

条款53: 不要忽视编译器的警告

条款54:让自己熟悉 TR1 在内的标准程序库

条款55: 让自己熟悉 Boost


简介

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

项目链接

github项目代码,点击下载: GitHub - YanfeiHao/EffectiveCpp: 80个改善程序设计的有效方法

一. 让自己习惯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

在这里插入图片描述

总结:

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

为了解决在成员函数和友元函数仍然还是能调用,声明其而不定义其是个好办法(会报链接错误)
为了将链接错误提前到编译期,需要将其继承一个阻止 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 的通过必要参数而实行的普通函数

在这里插入图片描述

总结

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

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

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

在这里插入图片描述

总结:

连锁赋值几乎被所有内置类型和标准库程序提供的类型, string,vector都遵守,因此
我们自定义的也最好共同遵守

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

自赋值是十分没有必要的,但也是代码漏点很多的一个问题

  1. 类中有heap资源, 编写代码时需要注意 异常安全性
  2. 自赋值的避免

在这里插入图片描述

总结:

确保当对象自我赋值时 operator= 有良好的行为(鲁棒性,通用性更强),其中技术包括

来源对象和目标对象的地址
精心周到的语句顺序
copy and swap
确保任何函数如果操作一个以上的对象,且多个对象都是同一个对象时,其行为仍然正确

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

如果拒绝编译器自动生成copying函数,如果你的代码不完全,他们也不告诉你!!!

一: 局部拷贝的错误

这里如果没有复制新添加的变量,编译器也是不会进行提醒的,因为你已经拒绝编译器进行为你生成 copy 函数

在这里插入图片描述
实验二: 继承拷贝的错误

继承一个基类后进行编写派生类的 copy 函数时应注意编译器不会进行对 派生类的基类部分自动拷贝
需要手写基类部分的复制

在这里插入图片描述

总结:

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

三. 资源管理

条款13: 以对象管理资源

基于对象的资源管理办法,是十分有效的

一: 体验基于对象资源管理的方法

自行编写的写法

获得资源后立即放入管理对象内, ”以资源取得时机便是初始化时机“ (RAII)
管理对象运用析构函数确保资源被释放。 (不论控制流如何,只要对象被销毁,析构函数必会调用)在析构函数中有异常,请遵循条款8

在这里插入图片描述

在这里插入图片描述

测试:

 在这里插入图片描述

总结:

  1. 为了防止资源泄漏,请使用 RAII对象, 他们在构造函数中获得资源并在析构函数中释放资源
  2. 两个常被使用的 RAII 对象 classes 分别是 std::shared_ptr 和 std::unique_ptr。shared_ptr 是一个 RCSP(reference count smart pointer)

条款14: 在资源管理类中小心 copying 行为

资源的类型并非都是 heap+based 资源时,你需要建立自己的资源管理类

一: 体验资源管理类中copying行为带来的问题

在这里插入图片描述

在这里插入图片描述

面对资源 copy 的动作有如下解决方案

  1. 禁止拷贝
  2. 对底层资源使用 RCSP ,引用计数法 (使用 shared_ptr)
  3. 深拷贝复制底部资源
  4. 转移底部资源的拥有权

二: 禁止 copy 行为

在这里插入图片描述

在这里插入图片描述

三: 引用计数 RCSP

 在这里插入图片描述

总结

  1. 复制 RAII 对象必须一并复制它所管理的资源,资源的copying行为决定RAII对象的copying行为
  2. 普遍而常见的 RAII class copying行为是: 抑制copying,施行引用计数

条款15: 在资源管理类中提供对原始资源的访问

提供原始资源的访问以方便客户

例如: shared_ptr 的get获取原始指针

显式返回原始指针
隐式直接进行转换(在类中定义 转换资源类型的 运算符)

在这里插入图片描述
测试:

在这里插入图片描述


总结

  1. APIs 往往要求访问原始资源,所以每个 RAII class 应该提供一个”取得其所管理之资源“ 的办法
  2. 对原始资源的访问可能经由显示转换或隐式转换。一般而言显示转换比较安全,但隐式转换对客户比较方便。


条款16: 成对使用 new 和 delete 时要采取相同形式

new 对应 delete, new[] 对应 delete[]
尽量少使用数组,多使用 vector string 标准库

在这里插入图片描述

条款17: 以独立语句将 newed 对象置入智能指针

一: 我们来演示一个复杂的错误(异常导致内存泄漏)

 在这里插入图片描述

c++ 语言的函数传参调用顺序弹性很大,可能会出现调用顺序如下的表现:

  1. new Widget
  2. priority() //假若这里抛出了异常,就会有内存泄漏问题
  3. shared_ptr

更好的办法

在这里插入图片描述
总结

尽量以独立的语句将 heap 资源置入 smart pointer

四. 设计与声明

条款18: 让接口容易被正确使用, 不易被误用

理想上,如果客户企图使用某个接口而却没有获得他预期的行为,这个代码不能通过编译

一:明智而审视地导入新类型对于预防“接口被误用” 有神奇疗效

在这里插入图片描述

在这里插入图片描述


二: 以函数替换对象,预先定义有效的 对象使得接口更具备安全性

在这里插入图片描述

另外: 除非有非常好的理由,否则应令 type 与 内置 type 提供一致的行为接口。

总结:

  1. 好的接口很容易被正常使用,不容易被误用
  2. “促进正确使用”的办法包括接口的一致性, 以及与内置类型的行为兼容
  3. “阻止误用” 的办法包括建立新类型, 限制类型上的操作,束缚对象值, 以及消除客户的资源管理责任
  4. shared_ptr 支持定制型删除其,可防范在不同DLL间出现的引用计数的问题,可被用来自动解除互斥锁等等资源问题

条款19: 设计 class 犹如设计 type

如何设计高效的 classes 呢, 遵循问题产出设计规范:

  1. 新的type的对象应该如何被创建和销毁? (构造函数析构函数)
  2. 对象的初始化和对象的复制该有什么样的差别? (构造函数与拷贝赋值运算符)
  3. 新type的对象如果被passed by value(以值传递),意味着什么? (copy构造函数)
  4. 什么是新typed “合法值”? (维护约束条件,要在构造,赋值,setter函数进行错误检查)
  5. 你的新 type 需要配合某个继承图系? (virtual 与 non-virtual的影响, 特别是析构函数 virtual)
  6. 你的新 type 需要什么类型的转换? (explict 与 non-explict,以及隐式转换运算符定义)
  7. 什么样的操作符和函数对该新type而言是合理的? (声明哪些函数, memeber函数还是否)
  8. 什么样的标准函数应该被驳回? ( 编译器自动生成的那些声明其为 delete)
  9. 谁该取用新的 type 的成员? (存取函数进行约束)
  10. 什么是新的 type 的“未声明接口”? ()
  11. 你的新 type 有多么一般化? (template 或 一整个types家族)
  12. 你真的需要一个新type吗? (如果只是为了扩充功能而进行派生,倒不如直接定义一个 non-member函数)

条款20: 宁以 pass-by-reference-to-const 替换 pass-by-value

一: 使用引用类型能提升效率问题

在这里插入图片描述

二:使用引用进行传参可以实现多态且避免对象切割问题

在这里插入图片描述

总结

  1. 尽量以 pass by reference, 即高效也支持多态,避免切割问题
  2. 内置类型以及 STL 迭代器和函数对象,使用 pass by value 比较适当,(属于c语言块的内容以c语言方式进行处理)

条款21: 必须返回对象时,别妄想返回其 reference

一: 拒绝返回局部作用域的局部变量的引用,那其实指向了一片不存在的区域

在这里插入图片描述

总结:

绝对不要返回 pointer 或 reference 指向的一个 local stack 对象,或者返回引用指向一个
heap-allocated 对象,或返回 pointer 或 reference 指向一个 local static对象而有可能同时
需要多个这样的对象

条款22: 将成员变量声明为 private

一: 如何对成员变量进行有效的控制

在这里插入图片描述

总结:

切记将成员变量设置为 private,这可赋予客户访问数据的一致性,可细微划分访问控制,允许约束条件 得到保证,并提供 class 作者以充分的实现弹性。

仅存在两种访问权限: private(提供封装)和 其他(不提供封装),protected 并不比 public 更具封装性。

条款23: 宁以non-member,non-friend 替换 member 函数

一: 在许多情况下 非成员非友元函数的做法比 member 好得多。

面向对象守则要求数据尽可能被封装,member函数封装性比 non-member 函数差,且需要重新编译整个类与相关联的,因为不想调用到该函数的实例对象也能调用到,但如果是非成员版本就是谁能用谁调用.
愈多东西被封装,我们改变那些东西的能力就越大,越方便,因为涉及到有限的客户代码

在这里插入图片描述

二: C++ 对于命名空间的妙用,适用 non-member,non-friend

将所有便利函数防止多个头文件内但隶属于同一个命名空间,意味着客户可以轻松扩展这一组便利函数,他们需要做的就是添加更多 non-member non-frient 函数到此命名空间内。

在这里插入图片描述

总结

宁可拿 non-member non-friend 函数替换member函数,这样做可能增加封装性,包裹弹性和机能扩充性。


条款24: 若所有参数皆需类型转换,请为此采用 non-member 函数

在这里插入图片描述

在这里插入图片描述

总结

若重载运算符函数的操作数皆需类型转换,就声明其为非成员函数


条款25: 考虑写出一个不抛异常的 swap 函数

一:. 实现一个 pimpl 手法的类, 并写出其 特例化的swap 函数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结:

  1. 提供一个 public swap 成员函数,让它高效地置换你的类型的两个对象值,这个函数不能抛出异常
  2. 在你的 class 或 template 所在的命名空间内提供一个 non-member swap,并令它调用上述 swap 成员函数
  3. 如果你正编写一个 class, 为你的class 特化 std::swap ,并令它调用你的 swap 成员函数
  4. 最后调用 swap 时,确保包含一个 using 声明符,以便让 std::swap 在你的函数曝光。然后不加任何 namespace 修饰符, 赤裸裸地调用 swap。
  5. 注意:不能抛出异常,因为swap是帮助 class 提供异常安全性的保障, 基于的条件就是 swap不能抛出异常

五. 实现

条款26: 尽可能延后变量定义式的出现时间


尽可能延后的真正意义
1: 不仅只是延后变量的定义,直到非带使用该变量的前一刻为止,甚至
应该尝试延后这份定义直到能够给它初值实参为止。
2. 不仅能够避免构造(析构)非必要对象,还能避免无意义的default的构造行为。

一: 1: 尽可能延后能优化代码

在这里插入图片描述

二:循环中所使用变量需不需要延后,还是提前定义?

在这里插入图片描述

总结
尽可能延后变量定义的出现,这样做可以增加程序的清晰度并改善程序效率。

条款27: 尽量少做转型动作

const_cast() //去除添加 const常量性(一般用于引用)
dynamic_cast() //动态类型转换,将指向派生类对象的基类指针(引用)转换为派生类指针(引用)
reinterpret_cast() //不可移植行为类似 c 语言转
static_cast() // 静态类型转换(隐式转换显示表现)

演示使用:

在这里插入图片描述

一: 派生类的virtual 动作先调用 base 的对应函数,容易出现的错误

在这里插入图片描述

二: 更容易避免使用 dynamic_cast 的两种方法

  1. 普遍的实现版本基于 class 名称之字符串比较, 当深度继承时,其strcmp就会变多,因此注重效率的代码应该 对 dynamic_cast 保持机敏和猜疑
  2. 之所以使用 dynamic_cast, 通常是因为你想在你认定为 derived_class 对象身上执行 derived class 操作函数,但你手上仅有一个 指向 base 的 pointer 或 reference

在这里插入图片描述

直接使用容器存储 指向 derived class 对象的指针(比较不切实际)

在这里插入图片描述
base class 提供 virtual 函数做你想对各个 Window 派生类做的事情,使用多态性质即可 (推荐)


直接使用容器存储 指向 derived class 对象的指针(比较不切实际)

base class 提供 virtual 函数做你想对各个 Window 派生类做的事情,使用多态性质即可 (推荐)

在这里插入图片描述
总结:

  1. 如果可以,请避免转型,特别是注重效率的代码中避免 dynamic_casts,如果有个设计需要转型动作,请试着发展无须转型的替代设计
  2. 如果转型是必要的,试着将它隐藏于某个函数背后, 客户随后可以调用该函数, 而不需要将转型放在他们的代码中, othertype ObjectToOther(object &obj)
  3. 宁可使用C++ style(新式)转型, 不要使用旧式转型, 前者很容易辨识出来,而且也有比较分门别类的职掌

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


handles(指针,引用,迭代器)

一: 探索返回对象内部成分带来的弊端

  1. 破坏封装性
  2. 导致空悬指针等情况(临时对象的内部竟然能被指向)

在这里插入图片描述

在这里插入图片描述

总结

避免返回 handles(ref, 指针,迭代器)指向对象内部,增加封装性,且帮助 const 成员函数像一个 const,减少 空悬 的发生(临时对象的内部竟然能被指向)


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

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

不泄露任何资源 2. 不允许数据败坏
异常安全性提供以下三个保证之一:

  1. 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构因此而败坏,所有对象都处于一种内部前后一致的状态
  2. 强烈保证:如果异常被抛出, 程序状态不改变。调用可能抛出异常的操作,如果调用失败应该恢复到调用之前的状态,调用成功就是完成成功
  3. 不抛出异常保证: 承诺绝不抛出异常, 因为他们总是能完成它们原先承诺的功能。(作用于内置类型) nothrow

一: 使用 copy and swap 实现强烈保证级别的异常安全性。

在这里插入图片描述

在这里插入图片描述 
条款30: 透彻了解 inlining 的里里外外

  1. inlining 在C++程序中是编译期行为
  2. 过度热衷inlining会造成程序体积体大,即使有虚拟内存,inline 造成的代码膨胀亦会导致额外的换页行为,降低指令高速缓存装置的击中率,降低效率。
  3. virtual 的调用会使得 inlining 落空 (运行期确定的多态行为,当然会使得inlining落空)
  4. 大部分调试器面对 inline 函数都束手无策(不存在的函数设置断点 真的很荒唐)

总结:

将大多数 inlining 限制在小型,被频繁调用的函数身上,可以使得日后的调试
和二进制升级更容易,也可使得潜在的代码膨胀问题最小化,使程序的速度提升机会最大化


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

一: 相依赖于声明式,而不要相依定义式实现一个类,遵循以下

  1. 如果能够使用 object references 或者 object pointers可以完成任务,就不要使用 objects。使用 objects 就需要定义该类型的定义式,在声明一个类时需要知道其sizeof大小
  2. 如果能够,尽量以 class 声明式替换 class 定义式

在这里插入图片描述

在这里插入图片描述

总结:

  1. 支持“编译依存性最小化”的一般构想是: 相依于声明式, 不要相依定义式。基于此构想两个手段是 Handle classes(将自身的实现作为另一个类,且将该类的指针作为自己的实现) 和 Interface classes(以抽象类作为接口)
  2. 程序库头文件应该以 “完全且仅有声明式” 的形式存在。

六. 继承与面向对象设计


条款32: 确定你的 public 继承素模出 is-a 关系

C++进行面向对象编程最重要的规则是:public inheritance 意味着是 is-a 的关系(正向一类)

一:验证 ia-a: D 继承 B,则每一个类型为D的对象本身也是类型为B的对象,反则不成立

在这里插入图片描述

在这里插入图片描述

虽然正方形继承自长方形,但适用于长方形的行为并不适合正方形, 这显然是非常不正确的,
继承后遵循 is-a 的行为,因此正方形必须能适应长方形的所有行为。

总结:

public 继承意味着 is-a,适用于 base class 的事情也一定适用于 derived class身上
未遵循is-a的代码即使编译通过,但不保证程序的行为是正确的

条款33:避免遮掩继承而来的名称

一:derived class 内的名称会遮盖 base classes 内的名称, 在public 继承下从来没有人希望如此

在这里插入图片描述

在这里插入图片描述

总结

derived class 内的名称会遮盖 base classes 内的名称, 在public 继承下从来没有人希望如此
为了让遮盖的名称再见天日,可使用using声明式子,或转交函数 类名::function


条款34: 区分接口继承和实现继承

public 继承观念由两部分组成: 函数接口继承和函数实现继承

一: 浏览接口继承与实现继承的具体体现

pure virtual函数的目的是为了让 derived class 只继承函数接口
impure virtual 函数的目的是让 derived class 继承该函数的接口和缺省实现
non-virtual函数的目的是为了令derived class 继承函数的接口及一份强制性实现(不变性,不应该被重新定义)

在这里插入图片描述
二: pure virtual 的定义 与 派生重写

我们可以为 pure virtual 提供一个定义,C++并不会发出怨言,但调用其途径仅 “调用时指定其class名称”

在这里插入图片描述
重要说明:pure virtual 函数必须在 derived class 重新声明,但也可以有自己的一份实现,需要显式调用而已, 适用于 接口与缺省情况并存的情况下 (比 impure class 好用)

在这里插入图片描述

在这里插入图片描述


总结:

  1. 接口继承和实现继承不同,在public 继承下, derived classes总是继承 base class 的接口
  2. pure class 函数只具体指定接口继承
  3. 简朴的 impure virtual 函数具体指定接口继承与缺省实现继承
  4. non-virtual 函数具体指定接口继承以及强制性实现继承
  5. 一个典型的程序有 80% 的时间花费在 20% 的代码上,请将心里放在那举足轻重的代码上。

条款35: 考虑 virtual 函数以外的其他选择


virtual 函数带来的虚函数表以及虚表指针的负担
其灵活性十分差,继承重写虚函数后不具有可变与可增内容灵活度

在这里插入图片描述
virtual 函数以外的其他选择:

一: 使用 NVI(Non-Virtual Interface) 手法实现 Template Method 模式(主张 virtual应该总是private)

私有impure virtual 使得派生类重新定义, member function 调用其 impure virtual 实现(且在调用前后都可以做额外的事情)

在这里插入图片描述
二: 以策略模式替换 virtual, 以基于对象的思路(std::bind + std::function)替换virtual+多态

策略模式的构成:

  1. GameCharacter 作为策略的执行者
  2. HealthCalcFunc 作为策略(角色生命值健康情况的计算)

演示1: 不使用bind+function,使用指针作为回调函数实现策略模式

Strategy 提供了有趣的弹性:

同一人物类型之不同实体可以有不同的健康计算函数。
某已知人物之健康指数计算函数可以在运行期变更。

在这里插入图片描述

在这里插入图片描述

演示2: 由 std::function+std::bind 完成 Strategy 模式

std::function 这样的对象可保持任何可调用物质函数指针,函数对象,成员函数指针(而非仅函数指针)且具有一定的兼容性,可调用物的参数可以隐式转换为其function声明的参数以及返回值能隐式转换为 function声明的返回值 就可以兼容

在这里插入图片描述

在这里插入图片描述

总结:

  1. virtual 的替换手法八廓 NVI 手法以及 Strategy 设计模式的多种形式。NVI 手法自身是一个特殊形式的 Template Method 设计模式
  2. 将机能从成员函数移到 class 外部函数,带来的一个缺点是,非成员函数无法访问。class 的 non-public 成员。
  3. std::function 对象的行为就像一般函数指针,这样的对象可接纳 “与给定之目标签名式” 兼容的所有可用调用物。

条款36: 绝不重新定义继承而来的 non-virtual 函数


绝不重新定义继承而来的 non-virtual 函数

条款37: 绝不重新定义继承而来的缺省参数值


由于派生类永远不会重新定义继承来的 non-virtual 函数,所以我们这条是针对 virtual 而言的

一: 缺省参数值执行的是静态绑定, 而不是运行期再次确定,缺省参数遵循调用者的静态类型而定

在这里插入图片描述

在这里插入图片描述

shape_sptr 的静态类型是 shape 的智能指针, 因此缺省值是 shape的draw的缺省值,而非 rectangle 的draw的缺省值,C++ 坚持以这种夸张的方式来运作是基于运行期效率来做的。

解决: 使用 NVI手法替换虚函数所表现的行为不是很满意的情况

base 的 public non-virtual 函数调用 private virtual, virtual 可被重新定义
我们让 non-virtual 函数指定缺省参数, private virtual 负责真正的工作

在这里插入图片描述

总结:

绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数都是静态绑定,
而virtual函数—你唯一应该覆写的东西-却是动态绑定。


条款38: 通过复合塑模出 has-a 或 “根据某物实现出”

复合包括

关联: 彼此并不负责对方的生命周期一般使用指针或者引用
聚合: 对象之间的关系表现为分为整体和局部, 整体部分并不负责局部对象的销毁
组合 : 对象之间的关系表现为分为整体和局部, 整体部分负责局部对象的销毁

is-a : 是继承,意味着 派生类必须能作为基类 完成基类能完成的所有功能
has-a: 是复合 挑选合适的关系帮我们完成更好的面向对象设计

一: 做一个必须以复合完成的功能实例, 以 list 实现 set

在这里插入图片描述
总结:

复合的意义和public继承完全不同
在应用领域,复合意味着 has-a(有一个),在实现领域,复合意味着(根据某物实现出)


条款39: 明智而审慎地使用 private 继承

一: 理解private 继承所能实现的效果

将 base class 中的所有成员变为 private 属性
编译器不会自动将一个derived class 对象转换为一个 base class 对象
private 继承意味着 根据某物实现出,而非 is-a 语义,只有实现部分被继承,接口不会被继承
编译器不会自动将一个derived class 对象转换为一个 base class 对象

在这里插入图片描述
private 继承意味着 根据某物实现出,而非 is-a 语义,只有实现部分被继承,接口不会被继承

 在这里插入图片描述

二: 其实使用 public 继承加复合 比 private继承 更好

  1. 阻止了派生类重新定义virtual的要求
  2. 将Widget的编译依存性降至最低

在这里插入图片描述
private 继承主要用于 “当一个意欲” 成为 derived class 者想访问一个
意欲成为 base class 者的protected成分,或为了重新定义一或多个 virtual 函数

空间最优化会促使你选择 private 继承而非 “继承加复合”
空类的sizeof为1,c++会为其安插一个char到空对象中,使得空对象间有一定的区分
EBO:空间基类最优化,将空类(未含成员的类)空间在继承后优化掉

在这里插入图片描述

考虑过所有方案后,仍认为 private 继承是“表现程序内两个 classes 之间的关系”的最佳办法,才使用

总结:

  1. private 继承意味着 (根据某物实现出)的语义, 比复合的级别低,但是当derived class 需要访问 protected base class 的成员,或者重新定义继承而来的 virtual 函数时,是合理的
  2. 和复用不同,private继承可用造成 empty base 最优化, 这对致力于 “对象尺寸最小化” 的程序开发者来说很重要。

条款40: 明智而审慎地使用多重继承


一: 多个base class 的成员名字相同造成的歧义

在这里插入图片描述
二. 菱形继承的问题(虚拟继承)

虚拟继承带来的影响

  1. 使用virtual继承的那些 classes 所产生的对象往往比使用 non-virtual继承的兄弟们体积大(安插共享指针),访问 virtual base classes 成员也慢
  2. virtual base 的初始化责任是由继承体系中的最底层(最高级别的派生类)负责。

在这里插入图片描述
关于虚拟继承的建议

  1. 第一,非必要不使用 virtual bases,平常请使用non-virtual继承
  2. 第二,如果你必须使用 virtual base classes,尽可能避免在其中放置数据,这样可用避免在 base classes 初始化发生错误

三: 正确使用多重继承

在这里插入图片描述

 在这里插入图片描述

使用 CPerson 表现人的实体,公有继承接口(IPerson)与 私有继承实现 (PersonInfo)
由于CPerson是转调Personinfo的实现来完成自己的接口,且需要重新定义 virtual 函数
那么就具备 (根据某物实现的语义), 使用private继承或者 public继承+复合 来完成

在这里插入图片描述

总结:

  1. 多重继承比单一继承复杂,且容易引起歧义,使用显示指定base class可解决
  2. virtual继承会增加大小,速度,初始化复杂度等成本,如果 virtual base classes。不带任何数据,那将是最具实用价值的情况
  3. 多重继承的确有正当用途,其中最常用即public 继承某个 interface 与 private 继承某个协助实现的 class 的情况

七. 模板与泛型编程


条款41:了解隐式接口和编译期多态


一对template参数而言,接口是隐式的,基于有效表达式,多态是通过template具现化和函数重载解析发生于编译器。

在这里插入图片描述

模板在实例化时会进行带入,之后发生编译器多态,对有效表达式进行检测
有效表达式即隐式接口(并非知道该隐式接口是否是有效,仅在具现化模板时进行编译期检测)

在这里插入图片描述
总结:

  1. classes 和 templates 都支持接口和多态
  2. class 而言接口都是显示的,以函数签名为中心,多态则是通过virtual函数发生在运行期
  3. 对template参数而言,接口是隐式的,基于有效表达式,多态是通过template具现化和函数重载解析发生于编译器。

条款42:了解 typename 的双重意义


一. typename 定义模板类型

在这里插入图片描述
二: 从属名称与 typename 指涉嵌套从属类型名称

 在这里插入图片描述

嵌套从属类型名称不需指定 typename 的情况:

在这里插入图片描述
对内嵌从属类型进行 起别名:

在这里插入图片描述

总结:

  1. 声明template参数时, 前缀关键字 class 与 typename 可互换
  2. 请使用关键字 typename 标识嵌套从属类型名称, 但不得在 base class lists(基类列)或 member initialization list(成员初始值列)内以它作为 base class 修饰符。

条款43: 学习处理模板化基类内的名称


一: 善用模板特例化解决特殊情况

在这里插入图片描述

这里: 如果 Company 没有 sendClearText,就会使得调用失败

在这里插入图片描述

使用模板特例化完成针对 CompanyZ 的MsgSender(使Company具现化在定义时)

在这里插入图片描述

二: C++ 模板继承时由于 base class 并没有具现化,C++并不知道继承的究竟是什么类,因此其内含的 members全部被隐藏, 因此继承而来的 sendClearMsg 会被隐藏

在这里插入图片描述

总结:

  1. 对于特别的模板参数类型可采用模板特例化完成
  2. 可在派生template类内通过 this-> 或 using 暴露其 base class template 成员名称

条款44: 将与参数无关的代码抽离 templates


一. 非类型模板参数会带来代码膨胀

在这里插入图片描述

二: 共性与变性的分析, 对类以 private 继承 或 复合 来抽离代码,并以函数参数或class成员变量替换 template 参数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

三. 类型模板参数也会导致膨胀,比如 vector 与 vector, 但只要二进制表述相同(参考指针的空间字节数)我们可以实现共享码

在这里插入图片描述

总结

  1. Templates 生成多个classes 和 多个函数,所以任何 template 代码都不应该与 某个造成膨胀的 template 参数形成相依关系
  2. 因非类型模板参数造成的代码膨胀, 往往可以消除, 做法是以函数参数或class成员变量替换templates参数
  3. 因类型参数而造成的代码膨胀,往往可降低,做法是以相同二进制表述的具现类型共享实现码。

条款45: 运用成员函数模板接受所有兼容类型


一:同一个template的不同具现体之间不存在固有关系,需要定义泛化的隐式转换

具现体的基本类型存有 转换(派生类指针到基类指针的转换),但具现体并不具备

在这里插入图片描述

 在这里插入图片描述

解决: 写出一个泛化copy构造函数来兼容,限制工作交给实际的类型去转换

在这里插入图片描述

在这里插入图片描述

二: 泛化的 copy 构造函数并不会阻止编译器生成它们自己的 copy 构造函数, 如果你想完完全全控制 copy动作,请写出泛化版本与非泛化版本

总结:

  1. 请使用 member function templates(成员函数模板)生成 ”可接受所有兼容类型“ 的函数泛化的 copy 构造函数并不会阻止编译器生成它们自己的 copy 构造函数,
  2. 如果你想完完全全控制 copy动作,请写出泛化版本与非泛化版本

条款46: 需要类型转换时请为模板定义非成员函数


一. 复现 24 (”唯有non-member函数才有能力在所有实参上实施隐式类型转换“)在template不适用

在这里插入图片描述

原因: template 在实参推导过程中并不考虑”通过构造函数而发生的隐式类型转换“,因此 operator*在该情况下并不会被推导出

二. 解决: 以template 相关的 ”函数支持所有参数之隐式类型转换“时,请将那些函数定义为 class template 内部的friend函数

在template class 中指涉 operator(**)函数为友元,由于类模板推导不依赖 tempalte 实参推导(施行于 function templates上),所以编译器总是能够在class Rational 具现后找到 友元函数 operator*的声明 (并因此缓和 template 实参推导)

注意: friend 仅代表声明, 我们需要定义,可以在友元声明处直接定义,或定义额外的non-member供友元调用

这里解释一下

为了让类型转换可能发生在所有实参上,我们需要一个 non-member 函数,为了让
这个函数被自动具现化, 我们需要将它声明在 class 内部,而在 class 内部声明 non-member 的唯一方法是友元。

在这里插入图片描述

在这里插入图片描述

总结:

当我们编写一个 class Template, 而它所提供之 ”与此template相关的“ 函数支持 ”所有参数之隐式类型转换“ 时,请将那些函数定义为 ”class template“ 内部的friend 函数。


条款47: 请使用 traits classes 表现类型信息

一: 迭代器类型的区分与traits(类型萃取技术)的实现

在这里插入图片描述
STL 中 通过萃取容器得到其 iterator 类型之后才能实施不同的算法
萃取技术的实现:

  1. 确定若干你想将来可取得的类型相关信息
  2. 为该信息选择一个名称
  3. 提供一个 template 和一组 特化版本(有需要的话),内含你希望支持的类型信息

在这里插入图片描述

使用:

在这里插入图片描述
二: 模仿 STL 中的做法,使用重载解决 CharacterTraits::category 在编译阶段能完成
但由于if 的原因却是推迟到运行期核定的问题 得到解决

在这里插入图片描述

三: 如何正确使用一个 traits class

  1. 建立一组重载函数(劳工)或函数模板,彼此之间的差异仅在各自的 traits 参数,令每个函数实现码与 其接收之 traits 信息相应和
  2. 建立一个控制函数(身份像工头)或函数模板(advance),它调用上述那些 ”劳工函数“并传递 traits class 所需信息

总结:

  1. Traits classes 使得 ”类型相关信息“ 在编译期可用, 他们以 templates 和 ”templates特化“ 完成实现
  2. 整合重载技术后, traits classes 有可能在编译期对类型执行 if…else 测试

条款48: 认识 template 元编程


一: 了解什么是 template 元编程(TMP template metaprograming)

简介: TMP 是编写 模板程序 并执行于编译期的过程(也可以是说执行于C++编译器内的程序)。
优点:执行与编译期,检测错误更早,程序更高效,较小的执行文件,较短的运行期,较少的内存需求。
缺点:编译时间变长。

二: TMP 实现计算阶乘

在这里插入图片描述

总结:

  1. TMP 可将工作由运行期移到编译期,因而得以实现早期错误侦测和更高的执行效率
  2. TMP 被用来 生成 ”基于政策选择组合“ 的客户定制代码,也可以避免生成对某些特殊类型并不适合的代码:

八. 定制 new 与 delete


条款49: 了解 new-handler 的行为


一: 认识 new-handler 和 set_new_handler,并懂得设计一个 new-handler

new-handler: 当operator new 抛出异常以反映一个未获满足的内存需求之前,它会现调用一个客户指定的错误处理函数,一个所谓的 new-handler

set_new_handler: 参数是指针,用于传入指定的 new-handler函数,返回值是( 被替换的那个 new-handler)

std下的标准库函数声明,模拟

在这里插入图片描述

使用 set_new_handler

在这里插入图片描述
二: 设计一个良好的 new-handler

当operator new无法满足内存申请时,它会不断调用 new-handler 函数,直到找到足够内存
那就是一个设计良好的 new-handler 函数必须做以下事情:

  1. 让更多内存被使用: 如果operator new 失败,下一次的分配动作可能成功。策略: 程序一开始就分配一大块内存,而后当 new-handler 第一次被调用,将它们释放给程序使用
  2. 安装另一个new-handler, 如果目前这个new-handler 并不能满足,让它有能力知道另外的 new-handler可以分配。策略: 令new-handler 修改 “会影响 new-handler行为” 的static数据,namespace数据或 global 数据
  3. 卸除 new-handler: 将 null 指针传给 set_new_handler ,一旦没有安装任何任何的 new-handler, operator new 会在内存分配不成功时抛出异常
  4. 抛出 bad_alloc(或派生自 bad_alloc) 的异常, 这样的异常不会被operator new 捕捉,因此 会被传播到内存索求处。
  5. 不返回,通常调用abort 或 exit

三. 根据每个 class 不同定制不同的内存分配失败情况

1. 首先需要 class 提供自己的 set_new_handler 和 operator new

set_new_handler 使得客户得以指定 class 专属的 new-handler
operator new 确保在分配 class 对象内存的过程中以 class 专属之 new-handler 替换 global new-handler

在这里插入图片描述

set_new_handler 任务

  1. 设置新 new-handler
  2. 返回旧 old-handler

在这里插入图片描述
operator new 任务

1.(更换错误处理函数) 调用标准 set_new_handler 设置自身保存的 current_handler,
2. (执行内存分配) 分配失败则 global operator new 会调用 current_handler
3. 其中 new-handler 在以异常的方式处理或退出operator new时,应注意将 class 中的 current_handler 回复到第1步设置之前
(保证不影响接下来的内存分配动作)

注意: 第3点使用RAII处理更好

在这里插入图片描述

使得更通用: 由于该动作并不因 class 的不同而不同, 将 Widget 的 operator new 与 set_new_handler 动作进行复用是必要的

在这里插入图片描述

在这里插入图片描述

三. nothrow 形式

 在这里插入图片描述


总结:

  1. set_new_handler 允许客户指定一个函数,在内存分配无法获得满足条件时被调用
  2. Nothrow new 是一个颇为局限的工具,因为它只适用于内存分配,后继的构造函数调用,还是可能抛出异常。

条款50: 了解 new 和 delete 的合理替换时机


总结:

  1. 为了检测运用错误
  2. 为了收集动态分配内存之使用统计信息
  3. 为了强化效能
  4. 为了增加分配和归还的速度
  5. 为了降低缺省内存管理器带来的空间额外开销
  6. 为了弥补缺省分配器中的非最佳齐位
  7. 为了将相关对象成簇集中 (减少内存页错误的出现频率)
  8. 为了获得非传统的行为

条款51: 编写 new 和 delete 时需固守常规


一: 编写 operator new 需注意的规矩

内含一个无限循环, 并在其中尝试分配内存,无法满足时调用 new-handler
处理 0 bytes 申请
Class专属版本应该处理 ”比正确大小的(错误)“ 申请
operator new(伪码)

在这里插入图片描述
基本实现版本:

在这里插入图片描述

二. 编写 operator delete 需注意的规矩

C++保证删除 null 指针永远安全
Class专属版本应该处理 ”比正确大小的(错误)“ 申请
伪码:

在这里插入图片描述
基本实现:

在这里插入图片描述

总结:

  1. operator new 应该内含一个无穷循环, 并在其中尝试分配内存,如果它无法满足内存需要。就应该调用 new-handler,它也应该有能力处理 0 bytes申请, Class 专属版本则还应该处理。”比正确大小更大的(错误)申请“
  2. operator delete应该在收到 null 指针时不做任何事, Class 专属版本则还应该处理。”比正确大小更大的“ (错误) 申请。

条款52: 写了 placement new 也要写 placement delete


一: 关于使用 new 因构造函数抛出异常出现的内存泄漏问题

placement new 要与 placement delete 对应 才能使得运行期系统寻找到 处理异常导致内存泄漏问题。

问题:
new 一个对象有两部,一部是分配内存,一步调用对象的构造函数

如果内存分配成功,但构造函数抛出异常,内存会交付给运行期系统处理,但需要对应的 delete 版本
如果使用了 placement new ,但未定义对应的 placement delete 就会出现问题
解决: 定义参数个数与类型相同的 placement new 与 placement delete

在这里插入图片描述

二. 解决派生类隐藏 Base 的 placement new 与 placement delete版本

问题:Base 声明的 placement new 与 placement delete 会被 Derived 定义的隐藏掉
标准形式的 operator new 与 operator new 也会因为 Class 声明而被隐藏

解决: 将正常形式的 new和delete 全部放在 一个 Base class里
Derived class 声明 placement new 与 delete, 且使用using声明式子将 Base class 中的
operator new 和 delete 暴露

在这里插入图片描述

总结:

  1. 当你写一个 placement operator new, 请确定也写出了对应的 placement。operator delete, 如果没有这样做,你的程序可能会发生隐微而时断时续的内存泄漏
  2. 当你声明了 placement new 与 delete, 请确定别无意识第遮掩了 他们的正常版本

九. 杂项讨论

条款53: 不要忽视编译器的警告

条款54:让自己熟悉 TR1 在内的标准程序库

条款55: 让自己熟悉 Boost

————————————————
版权声明:本文为CSDN博主「Handling[韩镇江]」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/chongzi_daima/article/details/109519635

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《更有效的C语言编程设计的35个有效方法》是一本非常实用的书籍,它总结了35个提高C语言编程设计能力的有效方法。这本书结合实际编程经验,从不同角度介绍了如何更高效地利用C语言进行软件开发。 该书首先从代码的可读性和可维护性方面提出了一些方法。比如,合理命名变量和函数、遵循一定的代码风格、使用注释等,这些方法可以使代码更易于理解和修改,提高工作效率。 其次,该书讲解了一些关于内存管理和指针的技巧。对于C语言开发者来说,内存管理是一个非常重要的技能。书中通过介绍如何正确使用动态内存分配函数、如何避免内存泄漏等方面来帮助读者提高内存管理的能力。 此外,该书还提供了一些提高代码质量和性能的方法。如代码复用、性能优化等。对于C语言开发者来说,写出高质量、高效率的代码是非常重要的,这本书可以帮助读者掌握一些技巧和原则。 总的来说,这本书内容丰富,通俗易懂,适合C语言的初学者和有一定基础的开发者阅读。它可以帮助读者全面提高C语言编程设计的能力,提升工作效率。无论是想从事C语言开发还是提升编程技能的人,都可以从中受益匪浅。 ### 回答2: 《more effective c: 35个改善编程设计有效方法(中文版) 》是一本非常实用的书籍,它提供了许多改善编程设计有效方法。以下是对该书的回答: 这本书共包含了35个方法,旨在帮助读者提高编程设计的效率。它首先介绍了良好的编程风格和规范,包括命名规则、代码布局、注释等。这些方法可以使代码更易于阅读和维护,并提高代码的可重用性和可扩展性。 接下来,该书介绍了一些常见的编程错误和陷阱,并提供了相应的解决方案。例如,它说明了内存管理的重要性,并给出了避免内存泄漏和悬挂指针的方法。 此外,该书还介绍了一些高级的编程技术和设计模式,如多线程编程、异常处理和继承等。这些方法可以帮助读者编写更健壮和可靠的程序,并提高程序的性能和响应能力。 另外,该书还强调了测试和调试的重要性,并介绍了一些常用的测试工具和技术。它提供了一些测试和调试的实用方法,帮助读者发现和修复程序中的错误和缺陷。 总的来说,《more effective c: 35个改善编程设计有效方法(中文版) 》是一本非常实用的书籍,它提供了许多实用的方法和技巧,帮助读者提高编程设计的效率。无论是初学者还是有经验开发者,都可以从中受益,并提升自己的编程能力。 ### 回答3: 《more effective c :35个改善编程设计有效方法(中文版) .pdf》是一本关于优化编程设计有效方法的书籍。 这本书共包含了35个有效方法,可以帮助程序员和设计师改进他们的工作。在这本书中,作者提供了一些实用的技巧和经验,帮助读者提高他们的编程设计技能。 这本书的价值在于它提供了实用的方法和步骤,读者可以按照这些方法和步骤进行实施,从而实现更有效编程设计。这本书涵盖了多个方面,包括代码的优化、错误的处理、算法的选择、设计模式的应用等等。 通过阅读这本书,读者可以了解到如何更好地组织和管理代码,以及如何选择合适的算法和数据结构来提高程序的效率。此外,这本书还介绍了一些设计模式和原则,读者可以学习如何使用它们来提高程序的灵活性和可维护性。 总之,这本书提供了一些实用的方法和技巧,帮助读者改进他们的编程设计技能。对于那些希望在编程设计领域取得更好成果的人来说,这本书是一个很好的参考资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值