effective c++学习笔记三

实现

 

 

条款26:尽可能延后变量定义式出现的时间


如果定义一个变量有构造函数和析构函数,那么即使这个变量没被使用,我们仍然需要承担这些成本,应该尽力避免。
不仅仅只是延后到用到位置,而更应该延后到能够给他初值实参为止。这样不仅可以避免构造和析构不必要的对象,还可以避免无意义的default构造函数行为,还可以附带说明变量的目的。

 

 

 

条款27:尽量少做转型动作


c++设计目标之一就是保证类型错误绝对不可能发生。理论上你的程序通过编译,就表示他并不企图在任何对象身上执行任何不安全无意义的操作。

 

c++类型的四种新式转型:
const_cast<T>(expression) 将对象的常量性移除
dynamic_cast<T>(expression) 安全向下转型,用来决定某对象是否归属于继承体系的某个类型,唯一无法用旧式语法进行的动作,也是开销较大的动作
reinterpret_cast<T>(expression) 意图执行低级转型,转型结果可能取决于编译器,不可移植
static_cast<T>(expression) 强迫隐式转换 non-const->const int->double等等

 

尽量使用新式转型,唯一使用旧式转型的是调用一个explicit构造函数将一个对象传递给一个函数时

 

任何一个类型转换,往往真的令编译器编译出运行期间执行的代码
当一个基类的指针指向派生类的对象,有时候两个指针的值并不相同,会有个偏移量在运行期间被施行于派生类的指针身上用以取得正确的基类指针值。
由此可见,单一的对象可能有一个以上的地址。
对象的布局方式和他们的地址计算方式随编译器的不同而不同。这意味着“由于知道对象如何布局”而设计的转型不一定行得通。

 

需要dynamic_cast通常是因为你想在一个你认定为派生类的对象上执行派生类操作函数,但是你手上只有一个指向基类的指针或者引用,你只能靠他们来处理对象。
一般有两种方式:
使用容器并在其中存储直接纸箱派生类对象的指针,一般是智能指针,如此便消除了通过基类接口处理对象的需要
或者在基类内提供virtual函数做你想对各个派生类做的事情。

 

如果可以,尽量避免转型 特别是在注重效率的代码里避免dynamic_casts
如果转型是必须的,那么把它隐藏在某个函数之后,而客户不需要将转型放进他们自己的代码内
尽量用c++新式转型,尽量少用旧时转型。

 

 

 

条款28:避免返回handles指向对象的内部成分


如果有函数返回了指针或者引用指向private内部数据,调用者就可以更改内部数据,而不用管该函数是否是const
成员变量的封装最多只等于返回其reference的函数的访问级别。如果函数返回一个reference,那么相当于是public,不管其变量是否是public
如果const成员函数传出一个reference,后者所指的数据与对象自身有关联,而他又被存储于对象之外,那么这个函数的调用者就可以修改那笔数据。
类似的,如果返回的是指针或者是迭代器,以上情况还是会发生。

 

也不要返回一个临时对象,因为会随着离开控制流而被销毁。

 

 

 

条款29:为异常安全而努力是值得的


当异常被抛出时,带有异常安全的函数会
1.不泄露任何资源
2.不允许数据破坏

 

异常安全函数提供下列三个保证之一
1.基本保证:如果异常被抛出,程序内的任何事物任然保持在有效状态下。没有任何对象或者数据会因此而破坏,所有对象都处于一种内部前后一致的状态
2.强烈保证:如果异常被抛出,程序状态不改变。如果函数成功,就是完全成功,如果函数失败,程序会回到调用函数前的状态
3.不抛掷保证:承诺绝不抛出异常,因为总能完成原先承诺的功能

 

策略:copy and swap
为你打算修改的对象创造一个副本,在副本上进行所有的修改,如果修改动作抛出异常,源对象未作改变。如果修改完成,则将修改后的对象置换掉原有对象(必须在一个不会抛出异常的函数中进行置换)。

 

如果系统内一个函数不具备异常安全性,那么整个系统就不具备异常安全性。

 

 


条款30:透彻了解inlining的里里外外


看起来像函数,但是不需要承担调用函数的开销
编译器优化机制通常就被设计用来浓缩那些不含函数调用的代码,当你inline某个函数,编译器就可能执行语境相关最优化。
inline只是对编译器的一个申请,而不是强制命令
隐式方式提出:将函数定义于class定义式内
显示方式:在定义前加上inline

 

inline函数通常一定被置于头文件内,大多数环境都将inline视为编译期行为。
大部分编译器将太过复杂(例如带有循环和递归)的函数inline,所有virtual函数都会使inline落空。
编译器通常不对“通过函数指针而进行的调用”进行inline
构造函数和析构函数往往是inline的糟糕候选人,即使是函数内什么都不写,编译器还是会在编译的时候加入很多内容。

 

 


条款31:将文件间的编译依存关系降至最低


将对象实现细目隐藏在一个指针背后,一个类中只含一个指针成员,指向其实现类(point to implementation)

class PersonImpl;
class Date;
class Person //作为一个handle class存在
{
    public:
    Person (const Date& birthday);
    private:
    std::tr1::shared_ptr<PersonImpl> pImpl;//指针指向实物
};
Person::Person(const Date& birthday):pImpl(new PersonImpl(birthday)){}

 

客户看不到person的实现细节,真正做到接口与实现分离
关键在于用“声明的依赖性”替换“定义的依赖性”,这是编译依存最小化的本质:现实中让头文件尽可能自我满足,万一不行就让他和其他文件中的声明式(而非定义式)相依赖。

 

策略:
1.如果可以用对象引用或者对象指针可以完成任务,就不要用对象。
2.如果可能,尽量用class声明式代替class定义式
3.为声明式和定义式提供不同的头文件

 

 

posted @ 2012-08-21 12:18  w0w0  阅读(183)  评论(0编辑  收藏  举报