Item M17:考虑使用lazy evaluation(懒惰计算法)
如:String s1 = "Hello";
String s2 = s1;
或许s2在程序中根本不会用到,所以在开始的时候不要调用s2的赋值构造函数来初始化,到后面要用,或者要改写s2的值的时候再调用,再进行赋值。
Item M18:分期摊还期望的计算
比如要计算某组数据的平均值。 我们可以在每一个新数值加入到程序里面的时候就去计算新的平均值。这样,当我们要取这个值的时候,可以立刻取出来。
Item M19:理解临时对象的来源
1、传值参数(pass-by-value parameter) 如:void fun(string s); 在用string对象传参数的时候就会产生临时对象。
2、返回值(return value)
3、隐式类型转换(Implicitly Typecast)
4、++ 和 --
解决方法:
1、避免使用传值参数,尽量使用传指针或传引用。
2、隐式类型转换的问题是定义了针对某个类型的构造函数,但是没有定义针对该类型的operator=所导致的,所以,构造函数和operator=必须成对出现。
3、尽量使用++i代替i++
4、利用编译器的优化
Item M20:协助完成返回值优化
(返回值优化)
一些函数(operator*也在其中)必须要返回对象。返回constructor argument而不是直接返回对象,能让编译器消除临时对象的开销。
Item M21:通过重载避免隐式类型转换
没有必要实现大量的重载函数,除非你有理由确信程序使用重载函数以后其整体效率会有显著的提高。
因为隐式类型转换可能导致额外的开销。 如,
Cls b;
Cls a = 1 + b;
当用1转为Cls类型时,就会调用Cls的隐式转换函数。这样就会有开销。
Item M22:考虑用运算符的赋值形式(op=)取代其单独形式(op)
op=的时候,不会产生临时对象,它将结果写到左边的参数里面。
Item M23:考虑变更程序库
如,有时候可以用stdio,特点是小,且速度快
有时候可用iostream,特点是类型安全,且可扩展
Item M24:理解虚拟函数、多继承、虚基类和RTTI所需的代价
Item M25:将构造函数和非成员函数虚拟化
虚拟构造函数是指能够根据输入给它的数据的不同而建立不同类型的对象。虚拟拷贝构造函数。
利用特性:被派生类重定义的虚拟函数不用必须与基类的虚拟函数具有一样的返回类型。
虚拟构造函数并不是与类名相同的函数,而是单独定义的一个函数,这个函数返回本类类型的对象指针,这个指针是个父类指针。在子类里面实现的时候,这个返回的指针是子类类型的指针。
就象构造函数不能真的成为虚拟函数一样,非成员函数也不能成为真正的虚拟函数。
它的实现方法:将某个成员函数声明为虚函数,然后在非成员函数里面通过父类调用这个虚函数,这样就可以根据传入的参数不同,而产生不同的行为了。
Item M26:限制某个类所能产生的对象数量
如使用单件模式,则可以限制类实例化个数为1。
将构造函数声明为private,可以阻止类建立对象。可以加入一个友元函数,来进行建立对象,以返回类对象指针,或类对象引用。
可以在类中加入静态的计数器,在构造函数中加1,析构函数中减1,则可以灵活的将把个类的实例限制在固定数目之内。
或可以单独创建一个计数器类,要计数的类都从这个类里继承。
Item M27:要求或禁止在堆中产生对象
要求在堆中建立对象: 可以通过将构造或析构函数声明为private来实现。一般将析构设为private或protected,这样,就无法自动释放对象,栈上的对象就会出错。
判断一个对象是否在堆中: 可以通过重载operator new操作符,在里面加上标识,如果调用了operator new,则将标志置位。不过这种方法不适合于创建数组,创建数组的时候只调用一次operator new (不过如果编译器支持重载operator new[]的话,也可以重载这个函数)。 或可以通过堆是向上生长,栈是向下生长。通过比较两个局部变量等,可以得知目前是在栈上还是堆上。
不过因为存在静态变量,所以无法区分是在堆上还是在静态变量区。
静态变量包括static声明的,在全局域定义的,在命名空间定义的。
“判断是否能够删除一个指针”比“判断一个指针指向的事物是否在堆上”要容易。C++使用一种抽象mixin基类满足了我们的需要。它通过将所有new返回的指针放入list中来实现。
const void *rawAddress = dynamic_cast<const void*>(this); //dynamic_cast可以将this指针变为一个指向当前对象起始地址的指针。在多继承或虚继承的时候,这个是非常有用的。
禁止堆对象: 将operator new与operator delete声明为private的。
Item M28:灵巧(smart)指针
Item M29: 引用计数
有时候多个对象的值完全相同,这时候就可以让它们共享一份数据,以节约空间。
如几个string对象指向同一个"hello"
当某个string被改写时,要将该对象与其它对象分离开来了。
写时拷贝:与其它对象共享一个值直到写操作时才拥有自己的拷贝。
也可以使用带引用计数的基类。
Item M30:代理类
扮演其它对象的对象通常被称为代理类.(如通过代理类来实现多维数组)
如stl中的vector<bool>特化版本,就使用了代理类。
因为vector<bool>作了优化,每个元素占用一个bit,所以对某个元素赋值true或false时,无法直接对它赋值,这时候就要使用代理类,先把值赋给代理类,然后代理类再操作具体的bit位。
Item M31:让函数根据一个以上的对象来决定怎么虚拟
比如a、b、c、d几个元素(类型为a_t)相互结合会产生不同的结果。如果用虚函数的话,则要在虚函数中对类的实际类型进行判断,然后根据不同的判断来调用不同的代码,而且当添加一个新类的时候,要更改大量代码,全部重新编译,而且完全不具备可维护性。
一个比较有效的解决办法就是使用非成员的碰撞处理函数。
即:map<pair<a_t, a_t>, fun>
当添加新的元素检测时,就在全局map表中添加。 维护起来相对比较容易,且可以动态的进行操作。
Item M32:在未来时态下开发程序
适应变化。
尽量用C++语言自己来表达设计上的约束条件,而不是用注释或文档。
处理每个类的赋值和拷贝构造函数,即使“从没人这样做过”。他们现在没有这么做并不意味着他们以后不这么做。
最小惊讶法则:努力提供这样的类,它们的操作和函数有自然的语法和直观的语义。和内建数据类型的行为保持一致:拿不定主意时,仿照int来做。
只要是能被人做的,就有人这么做。只有程序有可能出错,就一定会出错。(莫菲法则)
努力于可移植的代码。只有在性能极其重要时采用不可移植的结构才是可取的。
将你的代码设计得当需要变化时,影响是局部的。尽可能地封装;将实现细节申明为私有
避免导致虚基类的设计,因为这种类需要每个派生类都直接初始化它--即使是那些间接派生类。
未来时态考虑的约束:
·提供完备的类,即使某些部分现在还没有被使用。如果有了新的需求,你不用回过头去改它们。
·将你的接口设计得便于常见操作并防止常见错误。使得类容易正确使用而不易用错。
·如果没有限制你不能通用化你的代码,那么通用化它。
Item M33:将非尾端类设计为抽象类
将基类的析构函数设为纯虚析构函数是很常见的。 要想使基类不能被实例化,类中必须要包含纯虚函数,将析构函数设为纯虚的,是非常常用的方法。但是此时,必须要给纯虚析构添加实现,因为它们在派生类析构函数被调用时也将被调用。
如果基类没有数据成员,则将它设计为抽象类,因为没有数据的实体类是没有用处的。
不管怎么说啦,需要通过公有继承将两个实体类联系起来,通常表示需要一个新的抽象类。 即,如果从c1继承出c2,这时候应该这样考虑:
设计一个abstractC,然后使c1和c2都从里面继承出来。
在处理外来的类库时,你可能需要违背这个规则;但对于你能控制的代码,遵守它可以提高程序的可靠性、健壮性、可读性、可扩展性。
Item M34:如何在同一程序中混合使用C++和C
Item M35:让自己习惯使用标准C++语言