Effective C++ 笔记(二)
时间:2010-12-19 来源:苹果君
转型动作会产生副本。2)真正调用了A类的fun函数,而1)却调用了转型产生的临时对象的函数,如果涉及到修改对象的数据,真正被修改数据的是临时对象,而不是this。
1) static_cast<A>(*this).fun();
2) A::fun();
尽量避免dynamic_cast操作,尝试寻找其他替代方案。
如果转型是必要的,那就将其封装在一个函数当中,客户不需要负责转型,只需要调用该函数。
E28 避免返回handles指向对象内部成分
增强封装性,帮助const成员函数行为像个const,降低“虚吊号码”的可能性。
E29 为“异常安全”而努力是值得的
使用智能指针维护资源,使用copy and swap实现“强烈保证”
E30 透彻了解inlining函数的里里外外
将inline用于模板函数要格外小心,需要模板的所有具现化都能够inline。
virtual函数不能够被inline,在vs中,编译器将会忽略inline的申请。
如果以指针等方式来调用inline函数,编译器将不会用函数本体来替换语句。
对构造函数和析构函数进行inline必须考虑清楚,因为编译器还会做很多别的事情使他们变得巨大,例如构造函数内部调用基类的构造函数等等。
由于模块调用inline函数时,编译进去的可能是函数的本体,所以一旦inline函数被改动过,调用方也要重新编译。如果不是inline函数,只要重新链接就行了,如果inline函数是被动态加载的,根本就不用链接。
许多编译器选择在调试版本中拒绝inline,以便设置断点进行调试。
E31 将文件间的编译依存降至最低
通过添加一个pImpl实现接口与实现分离,这样实现改变不影响使用接口的外部代码。
尽量用Object Reference 和 Object Pointers 代替 Objects。
尽量以class声明式代替class定义式(如果声明一个函数时使用到类,是不用定义式的,只要声明式就好了,但是客户调用函数时,那个代码就需要定义式)。
具体做法是采用一个inerface class 或者handle class
E32 确定你的public继承塑模出is-a关系
E33 避免遮掩继承而来的名称
E34 区分接口继承和实现继承
E35 考虑virtual函数以外的其他选择
使用Strategy或者Template Method,使用非成员函数,或者函数指针等。
E36 绝不重新定义继承而来的non-virtual函数
E37 绝不重新定义继承而来的缺省参数值
只因virtual函数是动态绑定,而缺省参数值是静态绑定的。
E38 通过复合塑模出has-a或“根据某物实现出”
区分应用域和实现域。复合在不同的域塑模出不同的东西。
E39 明智而审慎地使用private继承
private继承纯粹是一种实现技术。
使用private继承而不是复合来塑模“is-implemented-in-terms-of”的情况:
基类有protected成员或者virtual函数,或者当基类是空的时候,节省空间。
E40 明智而审慎地使用多重继承
在调用函数的时候,C++首先确认这个函数对此调用而言是最佳匹配,找出最佳匹配之后才检验其可取性。
E6 析构函数里对指针成员调用delete
delete 空指针 程序不会做什么事。
对于指针成员,要做的事情:
在构造函数中初始化,无内存分配的先初始化为0
通过赋值操作符赋予指针新的内存之前,先删除现有内存
在析构函数中删除指针
E41
E42 了解typename的双重意义
使用typename标识嵌套从属类型名称。
E43 学习处理模板化基类内的名称
对于直接使用从模板基类继承而来的函数,应加上this指针表明该函数属于基类,或者使用using声明,又或者在函数上加个类名。(主要是防止基类的全特化版本并不提供预期的函数)
E44 将与参数无关的代码抽离template
因非类型模板参数而造成的代码膨胀,可以通过函数参数,或者类的成员变量替换template参数解决。
因类型参数造成的代码膨胀(比如int和long,指针),通过共享实现码解决。
E45 运用成员函数模板接受所有兼容类型
使用函数模板作为成员,声明拷贝构造函数等,使类可以接受其他兼容类型到该类的类型转换。成员函数模板不同于普通成员函数,尽管类已经声明了成员拷贝构造函数模板,编译器还是会生成默认的拷贝构造函数。
E46 需要类型转换时为模板定义非成员函数
在template实参推导过程中从不将隐式类型转换函数纳入考虑。
为了提供能够隐式转换所有类型的函数,将此函数定义在类的内部,并且声明为友元函数。
(为了链接成功,必须将函数的定义式加入类声明当中)
E47
E48
E49 了解new-hander的行为
内存不足抛出bad_alloc异常。
1 通过set_new_handler设置new操作的出错处理函数,new操作出错时,会调用出错处理函数,并不断循环尝试new。
2 通过使用set_new_handler(0)可以删除出错处理函数,这时的失败处理是抛出bad_alloc异常。
3 系统默认的new_handler是抛出bad_alloc异常。
4 标准 C++ 亦提供了一个方法来抑制 new 抛出异常,而返回空指针:
int* p = new (std::nothrow) int; // 这样如果 new 失败了,就不会抛出异常,而是返回空指针
if ( p == 0 ) // 如此这般,这个判断就有意义了
return -1;
E50 了解new和delete的合理替换时机
用来检测运行上的错误。
在new和delete的时候同时维护一串地址用于记录内存申请,可以查出内存泄漏等错误。
收集heap使用信息
改善效能
E51 编写new和delete时需固守常规
operator new必须有个无限循环,无法申请内存则要调用new-handler,处理0 bytes申请,处理比正确大小更大的错误申请。
operator delete收到null指针时不做任何事,处理比正确大小更大的错误申请。
E52 写了placement new 也要写placement delete
一个new,两个delete。
void* operator new(size_t size, int num) // placement new
void operator delete(void* memory) // normal delete
void operator delete(void* memory, int num) // placement delete
通过new操作符创建对象时,调用operator new,通过delete时,调用normal delete。
当placement new执行之后的初始化工作发生异常时,调用placement delete。