条款29:为“异常安全”而努力是值得的。
(1)异常安全 有两个条件,一个是不泄露任何资源,一个是不允许数据败露 。不泄露任何资源可以通过条款13的以对象管理资源来实现,而不允许数据败露,可以通过调整语句顺序等其他方式实现;
(2)异常安全函数提供三类保证:基本承诺、强烈保证、不抛掷异常保证。
基本承诺:异常被抛出时,程序内任何事物仍然保持有效状态,没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态;
强烈保证:如果异常被抛出,程序状态不改变 。程序的状态只有两种可能,像预期一样答道函数成功执行的状态,或者回到函数被调用前的状态;
不抛掷异常:承诺绝对不抛出异常,总是能够完成它们原先承诺的功能。如作用于内置类型上的操作都有nothrow保证。
异常安全码必须提供上述三种保证之一,对大部分函数而言,抉择往往落在基本保证和强烈保证之间。
条款30:深入了解inlining的里里外外。
(1)对于inline函数,编译器的优化机制可以减少函数调用的成本,但是对于该函数的每次调用都以函数本体替换会造成程序的体积膨胀。因此,inline适用于小型、被频繁调用的函数上 ,最小化代码膨胀的同时,提升程序的速度。
(2)不能因为templates函数和inline函数都可以出现在头文件 ,并不能因此为此templates声明为inline。inlining是大多数C++程序的编译器行为,而templates的具现化在什么 时期也取决于编译器,如果编译器的建置环境无法将要求的函数inline化,则会弹出警告信息。此外,虚函数也不适用inline,因为虚函数要在运行期才能决定调用哪个函数。
(3)构造函数和析构函数是糟糕的inling候选人。尤其对于存在继承关系的父子类 构造函数子类构造函数必先调用父类构造函数,是否会影响编译器的inline行为有待商榷,异常处理也难以控制。
程序设计者将函数声明为inline,必须掌握合乎逻辑的策略, 并谨慎使用。
条款31:尽可能地降低文件之间的编译依存关系,将接口和实现分离,避免比如修改class的实现导致使用过该class的程序都需要重新编译和链接。
(1)接口和实现分离的关键在于以“声明的依存性”替换“定义的依存性”,使得编译的依存性最小化,设计策略有:
使用对象引用或者对象指针,而不是对象本身;
使用某个类时,使用类声明式替换定义式;
为声明式和定义式提供不同的头文件,两者保持一致性,客户使用时只需要include声明文件,此种做法也适用于templates。
(2)常见的最小化编译依存性的处理方式有:Handle class 和Interface class:
Handle class 的方式是将所有函数转交给实现类完成,接口类中定义private的实现类指针/引用对象(智能指针更好),构造接口类的同时实例化实现类,这样便能在接口类中调用实现类的函数;
Interface class的方式是定义特殊的抽象基类,通常基类不带任何成员变量,也没有任何构造函数,只有一个virtual析构函数和一组纯虚函数,抽象基类必须提供factory工厂函数或者virtual构造函数返回指针指向动态分配的所得对象(子类对象),当然,创建不同的类型的子类对象取决于额外参数值、读取文件或者数据库等等。
以上两种方式的目的是解除接口和实现的耦合性,从而降低文件间的编译依赖性。
条款32:确定public继承塑造出is-a关系,即子类是父类的特殊类型。适用于父类(base class)身上的每一件事情一定也适用于子类(derived classs),因为每一个子类对象也是一个父类对象,反之则不成立。
条款33:避免遮蔽继承而来的名称。继承中,子类(derived class)的作用于被嵌套造父类(base class)的作用域内,编译器查找函数或者变量,从内层的local作用域由外扩大到外层的global作用域,名称相同的函数,即使参数类型不同,以及virtual还是no-virtual,都会有内部局部遮蔽外层的效果,这违反了上一条例的is-a关系。
为了不让父类的同名函数被遮蔽,可以使用using声明,比如 uing Base::mf1,指明是父类的函数,或者使用转交函数,子类同名函数中调用父类同名函数,比如:
class Derived:private Base{
public:
virtual void mf1()
{ Base::mf1());
}