effective_cplusplus
- 条款29
函数要达到异常安全,需要满足两个条件:
- 不泄露资源
- 不允许数据败坏(至少需要数据是有效的)
异常安全函数通常可以达到如下几个等级
基本承诺,异常被抛出,程序的任何事物仍然是有效地(比如计数器失效,有序数组失序),但当前程序的现实状态不确定,可能是几种状态之一。
强烈保证,只有未执行和执行两种状态,失败则会返回到未执行状态。
不抛掷保证,承诺不抛出异常,内置类型的所有操作都提供不抛掷保证,所以无法捕获到这些异常,如:
1
2
3
4
5
6
7
8
9
10
11
12
13try{
int a = 0;
int b = 5;
int c = b / a;
}
catch(exception e){
cout << "ztj: " << e.what();
}
return 0;
}
没有捕获到异常:
(yolo) tangjie.zhang@moveai415:effective_cpp$ ./b
Floating point exception (core dumped)达到强烈保证的一种实现,copy and swap,先创建对象副本,在副本上修改,(如果有异常,不会改变对象,如果无异常,接着处理),然后再不抛出异常的swap函数中置换。
- 条款30 inline
类中定义的函数隐式声明inline(具体是不是inline取决于编译器实现),包括friend函数。
加上明确的inline关键字达到显式声明。
优点:免除调用成本;利于编译器执行最优化(编译器会倾向于无outline函数执行最优化)
缺点:产生的目标码太大,降低缓存命中率,降低效率。改变inline函数后,程序必须重写编译链接。inline一般不支持调试,毕竟没法获得不存在的函数的地址。
inline函数应该放到头文件中(如果放到.c文件了,其他.c文件将无法按照inline的方式调用该inline函数。因为编译器通常是在编译过程中进行inlining,需要知道函数的实现,否则无法inlining,只能当在普通函数调用。类似的还有template,通常是编译器实例化,如果定义不在头文件,找不到定义,会报错)
inline是申请?太复杂的函数(包含递归,循环)无法inline,对virtual有调用的函数无法inline
- 条款31(还需要认真看下)
声明式:告诉编译器某类型存在,不提供该类型的详细信息,可用于,形参或返回类型,指针,引用,模板参数
定义式:定义式提供了类或函数的完整信息,定义某个对象时,置于class类中时都必须使用定义式(佐证1:编译器在编译时需要知道每个对象的大小),若只有声明式,会编译报错。
一个类如果包含了定义式,该类文件与包含的文件会形成编译依存关系,头文件改变,该类文件也要再次编译。如果只包含声明式,则不存在这些问题,毕竟该类只需要知道该类型存在就行了。
接口与实现分离:首先是一个接口类,其中包含一个指向实现类的指针。例如:
1 | #include <string> |
- 条款34
精确指定你想要 derived classes 继承的东西:
- 只继承接口:纯虚函数
- 继承接口和一份缺省实现:非纯虚函数。为避免派生类无脑继承,可以用纯虚函数+实现解决,如下代码。
- 继承接口和一份强制实现:非虚函数。
1 | #include <iostream> |
- 条款35
替换virtual函数的几个方案:
使用NVI(non virtual interface),在接口中包裹较低访问性(private或public)的virtual函数,有助于维持接口的一致性、实现封装等。
使用function或者函数指针等成员变量替换virtual函数。
将virtual替换为另一个继承体系的virtual函数,即strategy设计模式的传统实现手法。
软件模式 from kimi ai
创建型模式(Creational Patterns)
创建型模式主要关注对象的创建过程,试图以合适的方式创建对象。
- 单例模式(Singleton):确保一个类只有一个实例,并提供一个全局访问点。
- 工厂方法模式(Factory Method):定义创建对象的接口,让子类决定实例化哪一个类。
- 抽象工厂模式(Abstract Factory):创建相关或依赖对象的家族,而不需明确指定具体类。
- 建造者模式(Builder):构建一个复杂的对象,并允许按步骤构造。
- 原型模式(Prototype):通过拷贝现有的实例创建新的实例,而不是通过新建。
结构型模式(Structural Patterns)
结构型模式主要关注对象的组合,以及它们之间的关系。
- 适配器模式(Adapter):允许对象间的接口不兼容问题,使原本因接口不兼容而不能一起工作的类可以一起工作。
- 桥接模式(Bridge):将抽象部分与其实现部分分离,使它们可以独立地变化。
- 组合模式(Composite):将对象组合成树形结构以表示“部分-整体”的层次结构。
- 装饰器模式(Decorator):动态地给一个对象添加额外的职责。
- 外观模式(Facade):为子系统中的一组接口提供一个统一的高层接口。
- 享元模式(Flyweight):通过共享来高效地支持大量细粒度的对象。
- 代理模式(Proxy):为其他对象提供一个代替或占位符以控制对它的访问。
行为型模式(Behavioral Patterns)
行为型模式主要关注对象间的通信,以及它们如何相互协作。
- 责任链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。
- 命令模式(Command):将请求封装为一个对象,从而使用户可用不同的请求对客户进行参数化。
- 解释器模式(Interpreter):定义语言的文法,并且建立一个解释器,这个解释器可以解释和执行语言中的句子。
- 迭代器模式(Iterator):顺序访问一个聚合对象中的各个元素,不暴露其内部的表示。
- 中介者模式(Mediator):定义一个中介对象来简化原有对象之间的交互。
- 备忘录模式(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。
- 观察者模式(Observer):对象间的一对多依赖关系,当一个对象改变时,所有依赖于它的对象都会被通知到。
- 状态模式(State):允许对象在其内部状态发生改变时改变其行为。
- 模板方法模式(Template Method):在一个方法中定义一个算法的骨架,将一些步骤的执行延迟到子类。
- 访问者模式(Visitor):为一个对象结构(如组合结构)增加新能力。
- 条款36 && 37
non-virtual函数不应该被复写(虽然理论上可以),只有virtual才可以。
virtual函数定义是动态绑定的,而缺省参数(参数的值)是静态绑定的:派生类的virtual可以有不同的缺省参数,但默认会使用基类的参数,为避免混淆。可以使用nvi替换,public函数(一个)调用private virtual函数(一个或多个),在public用指定参数(只用写一次)调用private virtual函数,即可避免混淆。
- 条款38
复合意味着 has - a(应用域,遇到has a关系想到复合:人->电话号码),意味着 is-implementet-in-terms-of(实现域)。
public继承意味着 is - a(应用域,遇到is-a想到public继承:学生->人)。
实际使用时,先看设计层面,如果d满足b的所有特性,d是b的特例,再进行编码,d: public b;(可以有些virtual函数,这些virtual函数并不涉及到d,b的特性层面,比如四边形的特性只有四条边,而virtual函数可以有面积,周长,这不是特性)
如果d不满足b的所有特性,考虑其他方式。(比如,set,list,list可以有重复元素,set不能有,这在设计层面就不相同,虽然可以通过virtual函数弥补,但首先这不是一种好的设计,其次需要修改基类的相关函数,修改标准库是不允许的)
- 条款39 private继承
两个规则:
- 不会把派生类自动转化为基类
- 基类的所有成员,在派生类中会变成private属性。
private继承不代表is a,而是代表implemented-in-terms-of(根据某物实现出)。private继承仅仅是为了使用基类的某些特性,不是因为两者有任何关系。两者在设计领域没有意义,它的意义只在实现层面。
private继承和复合都意味着根据某物实现出,如何抉择:
尽量用复合
涉及到protected或者virtual时,可以用private继承。
public继承接口,private继承实现()
- 条款41
面对对象:显示接口,运行期多态(根据绑定的对象类型选择virtual函数)
泛型编程:隐式接口,编译期多态(根据传递的template参数选择函数)
显示接口由函数的签名组成(名称,参数类型,返回类型)。
隐式接口由有效表达式组成。(隐式接口不具体,只是粗略的限制了类型参数)
10.条款43
模板派生类一般拒绝在模板化基类中寻找继承而来的名称,因为他不确定对应的名称是否存在。三种方法:
- 使用this->func
- 使用using class
::func - 使用 class
::func (会导致virtual函数无法准确绑定)
这三种做法都可以向编译器承诺:基类会提供相应接口。将报错时间延后到编译期多态。
1 | 43.cpp:15:17: error: ‘baseFunc’ was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive] |
- 条款46
条款24 提到如果在函数调用时,需要将所有参数进行类型转换(如 “abd” + string(“75”)),只能定义non-menber函数,这在非模板情况下是成立的。如果参数对应的类别是模板类,转换前还需要进行模板推导,参数将不支持隐式转换,上述代码将失效。可以将该函数声明在参数对应的class内,class被定义时,会自动生成函数的声明,如果只提供了声明,需要手动提供定义,为此,需要将声明定义同时放到类内,friend函数。
- 条款47
迭代器分类:
- input,只能向前移动,只读,如istream_iterator
- ouput,只能向前移动,只写, 如ostream_iterator
- forward,只能前向移动,可读写,如单链表对应迭代器
- bidirectional,可双向移动,可读写,如set,multiset,map,multimap
- random,可随机访问,可读写,如vector,deque,string
这里介绍了一个方法,来讲if判断提前到编译时:将if判断的类型放到函数参数中,函数重载了若干个版本,可以在编译时用重载解析机制选择代码。
iterator_traits,提供了四分信息:iterator_category ,value—type,有 char_traits,numeric_limits。
c++有 traits classes ,包括is_fundamental
数组类型),以及 is_base—of<Tl,T2>(Tl 和 T2 相同,抑或 Tl 是 T2 的 base class)
- 条款48 模板元编程
其可以实现声明变量,执行循环,编写和调用函数。
循环由递归实现,递归模板具体化。如下:
1 | 120(yolo) tangjie.zhang@moveai415:effective_cpp$ cat 48.cpp |
作用:
- 确保度量单位正确。
- 优化矩阵运算
- 生成定制之设计模式。比如智能指针
- 条款49
operator new无法满足内存申请要求时,会调用new-handler,在其中,可以完成如下事情:
- 分配更多内存,比如程序启动时先分配一大块内存,需要时再返还。
- 安装另外的new-handler,自己无法处理时,可以在setnewhandler替换newhandler,下次operter new分配失败时,会调用新的。
- 卸载new-handler,传null,operator分配不成功会直接报异常。
- 抛出bad_alloc异常
- 不返回,调用abort或exit
CRTP(Curiously Recurring Template Pattern)
15.条款50
为什么要替换opertaor new,delete,三个理由:
- 检测很多错误,比如地址越界
- 强化性能,默认newdelete对每个人都适度的好,但不发达到最佳表现
- 收集使用数据分配大小,寿命,FIFO or LIFO or 随机归还,
- 条款51 编写new时的规则
当编译器遇到用于分配类型为 T
的对象的 new
运算符时,它会发出对 T::operator new( sizeof(T) )
的调用。(sizeof貌似是编译器自动加的,这印证了无法从类转换为size_t)
- 条款52
写下new时,调用了operator new 和 构造函数。
- 条款54
c++ 98 包含:
- STL C Standard Template Library, 标准模板库),覆盖容器 (containers 如 vector,
S七ring,rnap) 、迭代器 (iterators)、算法 (algorithms 如 find, sort, transform) 、
函数对象 (function objects 如 less, greater) 、各种容器适配器 (container
adapters 如 stack, priority_ queue)和函数对象适配器 (function object adatpe氏
如 rnern_fun,notl) 。 - Iostreams,覆盖用户自定缓冲功能、国际化 1/0,以及预先定义好的对象 cin, cout,
cerr和 clog 。 - 国际化支持,包括多区域 (multiple active locales) 能力。像 wchar_t (通常是
16 bits/char)和 wstring(由 wchar_七s 组成的 strings)等类型都对促进 Unicode
有所帮助。 - 数值处理,包括复数模板 (complex) 和纯数值数组 Cvalarray) 。
- 异常阶层体系 (exception hierarchy) ,包括 base class excep巨on及其 derived
classes logic error和 runtime error, 以及更深继承的各个 classes 。 - C89 标准程序库。 1989 C 标准程序库内的每个东西也都被覆盖千 C++内。
TR1 新组件(14个,对98的拓展,后被收入c++11)
- 智能指针,
- function,可以得到任何可调用物
- bind,
- 其他(第一组)杂项
- hash 表,用来实现 sets, multisets, maps 和 multi-map
- 正则表达式
- tuples ,pair可以用有两个对象,而tuples可以有任意个
- array,stl化的数组,
- mem_fn,
- reference_wrapper,
- 随机数
- 数学特殊函数
- c99兼容扩充
- 其他(第二组)由更精巧的 template 编程技术构成
- type traits,提供类型的编译期信息,
- result_of,推导函数调用的返回类型