M1:指针和引用的区别
指针可以指向空值,但引用必须指向一个对象。
char *pc=0;
char& rc=*pc;//非常有害
不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高,因为在使用引用前不需要测试它的合法性。
void printDouble(const double& rd)
{
cout << rd; // 不需要测试rd,它
} // 肯定指向一个double值
void printDouble(const double *pd)
{
if (pd) { // 检查是否为NULL
cout << *pd;
}
}
指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
当你重载某个操作符时,你应该使用引用。当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针。
M2:尽量使用C++风格的类型转换
M3:不要对数组使用多态
class BST { ... };
class BalancedBST: public BST { ... };
void printBSTArray(ostream& s,const BST array[],int numElements)
{
for (int i = 0; i < numElements; ) {
s << array[i]; //假设BST类
} //重载了操作符<<
}
M4:避免无用的缺省构造函数
M5:谨慎定义类型转换函数
有两种函数允许编译器进行这些转换:单参数构造函数和隐式类型转换运算符。
单参数构造函数是指只用一个参数即可以调用的构造函数。该函数可以是只定义了一个参数,也可以是虽定义了多个参数但第一个参数以后的所有参数都有缺省值。
隐式类型转换运算符只是一个样子奇怪的成员函数:operator 关键字,其后跟一个类型符号。
让编译器进行隐式类型转换所造成的弊端要大于它所带来的好处。
M6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别
前缀形式返回一个引用,后缀形式返回一个const类型。
后缀需要返回一个临时对象,所以用前缀效率高。
M7:不要重载“&&”,“||”,或“,”
M8:理解各种不同含义的new和delete
new操作符(new operator)和 new操作(operator new)
new操作符是内置的,不能改变它的含义,它要完成两部分功能。第一部分是分配足够的内存,第二部分是调用构造函数初始化内存中的对象。
new操作符调用operator new,主要用来分配内存,可以重载operator new
placement new可以在指定地址上构造对象
delete操作符与operator delete关系类似
注意:如果用placement new在内存中建立对象,应该避免在该内存中用delete操作符。
因为delete操作符调用operator delete来释放内存,但是包含对象的内存最初不是被operator new分配的,placement new只是返回转递给它的指针。
谁知道这个指针来自何方?而应该显式调用对象的析构函数来解除构造函数的影响:
对于数组:new操作符调用 operator new[]。operator new[]也可以被重载。
M9:使用析构函数防止资源泄漏
M10:在构造函数中防止资源泄漏
用对应的auto_ptr对象替代指针成员变量,就可以防止构造函数在存在异常时发生资源泄漏,你也不用手工在析构函数中释放资源,并且你还能象以前使用非const指针一样使用const指针,给其赋值。
M11:禁止异常信息(exceptions)传递到析构函数外
禁止异常传递到析构函数外有两个原因,第一能够在异常转递的堆栈辗转开解(stack-unwinding)的过程中,防止terminate被调用。第二它能帮助确保析构函数总能完成我们希望它做的所有事情。
Session::~Session()
{
try {
logDestruction(this);
}
catch (...)
{ }
}
M12:理解“抛出一个异常”与“传递一个参数”或“调用一个虚函数”间的差异
不论通过传值捕获异常还是通过引用捕获(不能通过指针捕获这个异常,因为类型不匹配)都将进行拷贝操作,即使被抛出的对象不会被释放,也会进行拷贝操作。抛出异常运行速度比参数传递要慢。
当异常对象被拷贝时,拷贝操作是由对象的拷贝构造函数完成的。该拷贝构造函数是对象的静态类型(static type)所对应类的拷贝构造函数,而不是对象的动态类型(dynamic type)对应类的拷贝构造函数。
class Widget { ... };
class SpecialWidget: public Widget { ... };
void passAndThrowWidget()
{
SpecialWidget localSpecialWidget;
...
Widget& rw = localSpecialWidget; // rw 引用SpecialWidget
throw rw; //它抛出一个类型为Widget
// 的异常
}
在catch子句中进行异常匹配时可以进行两种类型转换。第一种是继承类与基类间的转换。
第二种是允许从一个类型化指针(typed pointer)转变成无类型指针(untyped pointer),所以带有const void* 指针的catch子句能捕获任何类型的指针类型异常。
M13:通过引用(reference)捕获异常
M14:审慎使用异常规格(exception specifications)
模板和异常规格不要混合使用。
如果在一个函数内调用其它没有异常规格的函数时应该去除这个函数的异常规格。
用其它不同的异常类型替换unexpected异常
M15:了解异常处理的系统开销
使用try块,代码的尺寸将增加5%-10%并且运行速度也同比例减慢。应该避免使用无用的try块。
编译器为异常规格生成的代码与它们为try块生成的代码一样多。
M16:牢记80-20准则(80-20 rule)
用profiler程序识别出令人讨厌的程序的20%部分。
M17:考虑使用lazy evaluation(懒惰计算法)
M18:分期摊还期望的计算
M19:理解临时对象的来源
当传送给函数的对象类型与参数类型不匹配时会产生临时对象。
仅当通过传值(by value)方式传递对象或传递常量引用(reference-to-const)参数时,才会发生这些类型转换。当传递一个非常量引用(reference-to-non-const)参数对象,就不会发生。
建立临时对象的第二种环境是函数返回对象时。
M20:协助完成返回值优化
特殊的优化――通过使用函数的return 位置(或者在函数被调用位置用一个对象来替代)来消除局部临时对象――是众所周知的和被普遍实现的。它甚至还有一个名字:返回值优化。