<效率>
条款16:谨记80 – 20 法则
80 – 20 法则说:一个程序80 % 的资源用於20 % 的代码身上。
语句的执行次数和函数调用次数可以间接协助你了解你无法直接量测的软件行为。如果你无法直接量测动态内存的使用,那么知道内存分配函数和释放函数被调用的频率,至少也可以给你带来一部分的联想。
条款17:考虑使用lazy evaluation(缓式评估)
一旦你采用lazy evaluation,就是以某种方式撰写你的classes,使它们延缓运算,直到那些运算结果刻不容缓地被迫切需要为止。
在const memberr functions内修改data members的最好方法是,将成员变量声明为mutable,意思就是这样的字段可以再任何member function内被修改,甚至是在const member functions内。
条款18:分期摊还预期的计算成本
当你必须支持某些运算而其结果并不是总是需要的时候,lazy evaluation可以改善程序。当你必须支持某些运算而结果几乎总是被需要,或者其结果常常被多次需要的时候,over-eager evalution可以改善程序效率。
over-eager evaluation背后的观念是,如果你预期程序常常会用到某个计算,你可以降低每次计算的平均成本,办法就是设计一份数据结构以便能够既有效率地处理需求。其方式是:
- 将已计算好而有可能再被需要的数值保留下来,即caching;
- prefetching。
条款19:了解临时对象的来源
C++中真正的临时兑现是不可见的,即不会在你的源代码中出现。只要你产生一个non-heap object而没有为它命名,便诞生了一个临时变量。
临时变量通常发生于两种情况:
- 当隐式转换型别转换(implicit type conversions)被施行起来以求函数调用成功;
- 当函数返回对象的时候。
为了让函数调用得成功而产生的临时对象发生在于,传递某对象给一个函数,而其型别与它即将绑定上去的参数型别不同的时候。
只有当对象以by value方式传递,或是当对象被传递给一个reference-to-const参数时,这些转换才会发生。如果对象被传递给一个reference-to-non-const参数,并不会发生此类转换。
当程序员期望非临时对象被修改,此时如果编译器针对reference-to-non-const对象进行隐式型别转换,会允许临时对象被改变。
条款20:协助完成返回值优化(Return value optimization)
如果函数一定得以by-value方式返回对象,可以通过返回所谓的constructor arguments以取代对象。这样做能够让编译器消除临时对象的成本。
返回值优化:利用函数的return点消弭一个局部临时对象。如果该类函数作为内联函数,并且考虑到内联函数的特性的话,返回值优化类似于构造函数的调用。
条款21:利用多载技术避免隐式型别转换
C++存在许多游戏规则,其中一条就是:每个重载操作符必须接获至少一个用户定制型别的自变量。
条款22:考虑以操作符复合形式(op=)代替其独身形式(op)
一个有效的实现操作符重载的方式是,以操作符复合形式为基础实现其独身形式。
三个与效率有关的情况值得注意:
- 独身形式的操作符通过必须返回一个新对象,因此程序必须负担一个临时对象(局部对象)的构造和析构;而复合形式的则直接将结果写入其左端自变量,所以不需要产生一个临时对象来放置返回值;
- 如果同时提供某个操作符的复合版和独身版,你便允许你的客户在效率与便利性之间做取舍;
- 考虑返回值优化问题。
在C++中,未具名对象总是比具有名对象更容易被消除。所以当你面临具名对象(局部变量)或临时对象的抉择时,最好选择临时对象。
条款24:了解virtual functions、multiple inheritance、virtual base classes、runtime type identification的成本
vtbl(virtual table)通常是一个由函数指针架构而成的数组。程序中每一个class凡声明(或继承)虚函数者,都有自己的一个vtbl,而其中的条目(entries)就是该class的各个虚函数实现体的指针。
如果类C2集成基类C1,然后重新定义某些继承而来的虚函数,并加上新的虚函数。其vtbl内的条目(entries)将会指向对应于对象型别的各个适当函数,以及未被C2重新定义的C1虚函数。
virtual table pointer(vptr)的任务是可以指示每个对象相应于哪个vtbl。通过引用或指针调用虚函数,编译器必须产生代码来完成如下动作:
- 根据对象的vptr找出其vtbl。成本只有一个偏移调整(offset adjustment,以便获得vptr)和一个指针间接动作(以便获得vtbl);
- 找出被调用该函数在vtbl内对应指针。编译器会为每个虚函数指定了一个独一无二的索引,因此本步骤的成本只是一个差移以求进入vtbl数组;
- 调用步骤2所得指针所指向的函数。
需要注意内联函数和虚函数的区别:
- 内联(inline)意味着在编译期,将调用端的调用动以被调用函数的函数本体取代之(宏定义的函数化);
- 虚函数(virtual)意味着等待,直到运行期才知道那个函数被调用。
在多重继承情况下,一个对象之内包含多个vptrs(每个基类各对应一个)。在non-virtual base classes的情况下,如果derived class之于其base class有多条继承路径,则此base class的data member会在每一个derived class object体内复制滋生,每一个副本对应derived class和base class之内的一条继承路线。让base class成为virtual的话,可以消除这样的复制现象。
但是,virutal base classes可能招致另一个成本,因为其实现做法常常利用指针,指向virtual base class成分以消除复制行为,而你的对象内可能出现一个(或多个)这样的指针。
RTTI(runtime type identification)让我们得以在运行期获得objects和classes的相关信息,这些信息被存放在型别为type_info的对象内。可以利用typeid操作符取得某个class响应的type_info对象。