一、说明。
1、E指的是《Effective C++ 第二版》
2、M指的是《More Effective C++ 第二版》
二、总结
1、如果需要定制自己的operator new(通常是在需要分配大量的小对象,而且对性能要求特别敏感的程序里),具体的使用准则请参看 E7/E8/E9/E10。
(1)、全局的operator new 在<new>里定义,可以通过::operator new()调用。它的伪代如下
它将循环执行内存分配,跳出循环的唯一办法是内存分配成功或出错处理函数完成。
- 得到了更多的可用内存;
- 安装了一个新的new-handler(出错处理函数);使用set_new_handler()函数设置。默认情况下new_handler将为NULL,此时operator new将自动抛出std::bad_alloc异常。如果设置了一个new handler函数,但又分配足够的内存,或者不退出,或者不抛出异常,那operator new 将一直循环执行。
- 卸除了new-handler;
- 抛出了一个std::bad_alloc或其派生类型的异常;
- 或者返回失败。
默认情况下,new handler是NULL,operator new 将不会循环,而是抛出std::bad_alloc退出。如果你通过调用set_new_handler()函数设置了一个异常处理函数,在你的new handler函数里,你必须要遵循上面说的5个条件之一或者更多。不然operator new将一直循环的调用你的new handler函数。
2、为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符,或者如果你不需要此两个函数,那么就要把他们声明为private。
具体的使用准则请参看 E11。
因为默认的类里自动为你生成了这两个函数,而且是使用浅复制的形式。所以你将有可能遇到以下情况:
- 内存泄漏(即分配的内存没有指针指向,而又还没有删除)
- 多重删除
当实现赋值操作符时,准则是:一定要返回*this的引用 E15/E16/E17
否则会出现:
(1)无法连续赋值(因为要保持与普通=号的职责)
正确的标准写法是:
3、基类要有虚析构函数。E14
(1)否则将会导致:
指针指向的子类无法调用正确的析构函数(当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。)
(2)一般的做法是:当不需要要实例化基类时,最好把析构函数声明为纯虚析构函数(当然析构函数要有实现)。
(3)把非基类的析构函数声明为虚析构函数,会导致两种问题:第一是性能问题,因为有了虚拟函数表;第二是与其他语言之间的交互,此问题一般比较少遇到。
4、尽量使用初始化而不要在构造函数里赋值
const和引用数据成员只能用初始化,不能被赋值
性能上考虑,如果是赋值,那么将会有两次的赋值过程,一次是在类成员声明里,一次是在赋值里;而初始化只有一次。
5、参数的实质
例子:int function(int a,string b,string& c,string*d);
function(1,"b","c","d");
(1)、首先生成一个临时的对象。
对于第1个参数,是生成一个int a,然后把int值1赋值给a。所以改变a的值没有办法改变值1。
对于第2个参数,把"b"的值通过构造函数构造一个对象b,所以改变b的值也没有办法改变值"b";
对于第3个参数,生成一个string&对象,此引用的对象类似与指针,把"c"的地址传给string&对象,所以改变c的内容,将改变"c"的内容。但无法再次改变c的指向,因为c是一个引用。
对于第4个参数,生成一个string*对象,任何对象的指针的大小在一般的系统下都是4个字节大小,然后把"d"的地址传递给d,
所以改变d的内容将改变"d"的内容。但如果把d再赋值另外的地址,因为已经指向了另外一个地址,d对象的内容的改变将无法影响"d"。
结论:任何形式的参数传递都是值传递,但值的类型由参数的类型决定。或者是个对象,或者是个指针,或者是个引用。
6、尽量使用const;尽量传引用,不传值;函数重载和默认缺省参数要仔细考虑;不要对指针和数字类型重载;
7、类设计要点。
(1)C++编译器自动生成缺省构造函数、拷贝构造函数、析构函数、赋值运算符、取址运算符。如果不想使用这些默认定义,应该重定义他们或者直接使用private屏蔽。
定义一个类:
class Empty{};
等价于
(2)operator= 的设计。E15/E16/E17
- 检查给自己赋值的情况
- 对所有数据成员赋值
- 返回*this的引用
(3)、公有继承、存虚函数、虚函数、非虚函数的意义
纯虚函数意味着仅仅继承函数的接口。如果类C声明了一个纯虚函数mf,C的子类必须继承mf的接口,C的具体子类必须为之提供它们自己的实现。见E36。
简单虚函数意味着继承函数的接口加上一个缺省实现。如果类C声明了一个简单(非纯)虚函数mf,C的子类必须继承mf的接口;如果需要的话,还可以继承一个缺省实现。见E36。
非虚函数意味着继承函数的接口加上一个强制实现。如果类C声明了一个非虚函数mf,C的子类必须同时继承mf的接口和实现。实际上,mf定义了C的 "特殊性上的不变性"。见E36。
决不要重新定义继承而来的非虚函数 E37
虚函数的使用要特别注意,因为子类都默认继承了实现,所以虚函数的实现子类必须是要一致的。E36
8、非局部静态对象初始化。E47.
使用函数内定义的技巧保证初始化。
未完待续~~~~~