Effective & More Effective C++总结
2010-01-12 15:16
288 查看
一、说明。
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.
使用函数内定义的技巧保证初始化。
未完待续~~~~~
1、E指的是《Effective C++ 第二版》
2、M指的是《More Effective C++ 第二版》
二、总结
1、如果需要定制自己的operator new(通常是在需要分配大量的小对象,而且对性能要求特别敏感的程序里),具体的使用准则请参看 E7/E8/E9/E10。
(1)、全局的operator new 在<new>里定义,可以通过::operator new()调用。它的伪代如下
void * operator new(size_t size) // operator new还可能有其它参数 { if (size == 0) { // 处理0字节请求时, size = 1; // 把它当作1个字节请求来处理 } while (1) { 分配size字节内存; if (分配成功) return (指向内存的指针); // 分配不成功,找出当前出错处理函数 new_handler globalhandler = set_new_handler(0); set_new_handler(globalhandler); if (globalhandler) (*globalhandler)(); else throw std::bad_alloc(); } }
它将循环执行内存分配,跳出循环的唯一办法是内存分配成功或出错处理函数完成。
得到了更多的可用内存;
安装了一个新的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)无法连续赋值(因为要保持与普通=号的职责)
正确的标准写法是:
string& string::operator=(const string& rhs) { if (this == &rhs) return *this; base::operator=(rhs); // 调用this->base::operator= //但如果基类赋值运算符是编译器生成的,有些编译器会拒绝这种对于基类赋值运算符的调 //用E45。为了适应这种编译器,必须这样实现 //static_cast<base&>(*this) = rhs; // 对*this的base部分 y = rhs.y; return *this; }
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{};
等价于
class Empty { public: Empty(); // 缺省构造函数 Empty(const Empty& rhs); // 拷贝构造函数 ~Empty(); // 析构函数 ---- 是否 // 为虚函数看下文说明 Empty& operator=(const Empty& rhs); // 赋值运算符 Empty* operator&(); // 取址运算符 const Empty* operator&() const; };
(2)operator= 的设计。E15/E16/E17
检查给自己赋值的情况
对所有数据成员赋值
返回*this的引用
class_a& class_ar<t>::operator=(const namedptr<t>& rhs) { if (this == &rhs) return *this; // 见条款17 // assign to all data members name = rhs.name; // 给name赋值 *ptr = *rhs.ptr; // 对于ptr,赋的值是指针所指的值, // 不是指针本身 return *this; // 见条款15 }
(3)、公有继承、存虚函数、虚函数、非虚函数的意义
纯虚函数意味着仅仅继承函数的接口。如果类C声明了一个纯虚函数mf,C的子类必须继承mf的接口,C的具体子类必须为之提供它们自己的实现。见E36。
简单虚函数意味着继承函数的接口加上一个缺省实现。如果类C声明了一个简单(非纯)虚函数mf,C的子类必须继承mf的接口;如果需要的话,还可以继承一个缺省实现。见E36。
非虚函数意味着继承函数的接口加上一个强制实现。如果类C声明了一个非虚函数mf,C的子类必须同时继承mf的接口和实现。实际上,mf定义了C的 "特殊性上的不变性"。见E36。
决不要重新定义继承而来的非虚函数 E37
虚函数的使用要特别注意,因为子类都默认继承了实现,所以虚函数的实现子类必须是要一致的。E36
8、非局部静态对象初始化。E47.
使用函数内定义的技巧保证初始化。
class FileSystem { ... }; // 同前 FileSystem& theFileSystem() // 这个函数代替了 { // theFileSystem对象 static FileSystem tfs; // 定义和初始化 // 局部静态对象 // (tfs = "the file system") return tfs; // 返回它的引用 }
未完待续~~~~~
相关文章推荐
- &lt;More Effective C++&gt;笔记--基础
- More Effective C++----异常 & (9)使用析构函数防止资源泄漏
- More effective C++学习总结
- More Effective C++----(4)避免无用的缺省构造函数 & (5)谨慎定义类型转换函数
- More Effective C++之Item M7:不要重载“&&”,“||”, 或“,”
- More Effective C++----效率 & (16)牢记80-20准则(80-20 rule)
- 《More Effective C++》Rule7:千万不要重载 &&, ||, 和 ,操作符
- <<More Effective C++>>笔记C++技巧1
- More Effective C++----(1)指针与引用的区别 & (2)尽量使用C++风格的类型转换 & (3)不要对数组使用多态
- More Effective C++----技巧 & (25)将构造函数和非成员函数虚拟化
- More Effective C++总结(2):运算符
- More effective C++学习总结
- C++之尽量不要重载&&,||或者,运算符(7)---《More Effective C++》
- &lt;More Effective C++&gt;笔记--其他杂项
- More Effective C++----(12)理解"抛出一个异常"与"传递一个参数"或"调用一个虚函数"间的差异
- More Effective C++总结(1):基础议题
- &lt;More Effective C++&gt;笔记--技巧
- More Effective C++----(7)不要重载"&&"、"||"、","
- More Beyond "More Effective C++: catch exception through reference"
- More Effective C++总结(3):异常