`
nathan09
  • 浏览: 144668 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

【读书笔记】Effective C++-2 构造/析构/赋值运算(之四)

 
阅读更多

Effective C++读书笔记

--By Nathan.Yu 2007-11-24--

2 构造/析构/赋值运算(之四)

条款08:别让异常逃离析构函数

C++并不禁止析构函数吐出异常,但它不鼓励你这样做!

理由:

设:std::vector<Widget> v;

假设v中有多个Widget,在销毁v的过程中,当有2个以上的Widget在销毁的时候抛出异常,对C++而言就太多了。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。

只要是在析构函数吐出异常,即使并非使用容器,程序也可能过早结束或出现不明确行为。

如果程序在析构函数中捕获异常,“强迫结束程序,即调用abort”是个合理的选项。可以抢先制“不明确行为”于死地。

另,将异常吞没是个坏主意。因为它压制了“某些动作失败”的重要信息。为了让这成为一个可行的方案,程序必须确保能够继续可靠地执行,即使在遭遇并忽略一个错误之后。

更好地做法是,赋予客户一个处理异常的机会。如果某个操作必须在析构函数中使用,它又可能抛出异常,则将这个操作作为一个公共的接口,允许客户手动调用,让客户自处理异常。在析构函数中,判断客户是否调用过该函数,如若没有,则再次调用,并对可能抛出的异常进行处理。

总之,如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。

请记住:

1、 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它或结束程序。

2、 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作

【备注】参见More Effective C++ 条款十一:禁止异常信息(exceptions)传递到析构函数外

条款09:绝不在构造和析构过程中调用virtual函数

<注意>这是C++JavaC#不同的一个地方

1Base class构造期间virtual函数绝不会下降到derived classes阶层。

2、在base class构造期间,virtual函数不是virtual函数。

3、在derived class对象的base class构造期间,对象的类型是base class而不是derived class。因此,不止vritual函数会被编译器解析为base class,若使用运行期类型信息(如dynamic_casttypeid),也会把对象视为base class类型。

4、对象在derived class构造函数开始执行前不会成为一个derived class 对象。

相同道理也适用于析构函数。

解决方案:改用non-virtual函数,并要求derived class构造函数传递必要信息给base class构造函数,base class构造函数中调用non-virtual,该函数根据不同的信息处理不同的derived class

请记住:

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class

条款10:令operator=返回一个reference to *this

协议:为了实现x=y=z=15之类的“连锁赋值”,赋值操作符必须返回一个引用指向操作符的左侧实参。

这个协议适合于所有赋值相关运算operator op=)。

请记住:

令赋值(assignment)操作符返回一个reference to *this

条款11:在operator=中处理“自我赋值”

什么情况发生“自我赋值”?

情况1Widget w; w = w;

情况2a[i] = a[j]; //潜在的自我赋值,当i == j

情况3*px = *py; //pxpy恰巧指向同一个对象。

详细分析operator=时可能出现的情况:

假设类:

class Bitmap{……};

class Widget{

……

private:

Bitmap* pb;

};

operator=的不同实现版本:

版本1一份不安全的实现版本,不具“自我赋值安全性”,不具“异常安全性”

Widget&

Widget::operator=(const Widget& rhs)

{

delete pb;

pb = new Bitmap(*rhs.pb);

return *this;

}

这里的自我赋值是,*thisrhs有可能是同一个对象。果真如此delete就不只是销毁当前对象的bitmap了,也销毁rhspb。那么*this将持有一个已被删除的对象。

版本2使用“证同测试(Identity test)”阻止版本1中的错误,达到“自我赋值”的检测目的,具“自我赋值安全性”,不具“异常安全性”

Widget&

Widget::operator=(const Widget& rhs)

{

if(this == &rhs) return *this;

delete pb;

pb = new Bitmap(*rhs.pb);

return *this;

}

这里,new Bitmap可能导致异常(不论是因为分配内存不足或因为Bitmap的拷贝构造抛出异常),Widget会持有一个指针指向一块被删除的Bitmap

版本3忽视“自我赋值”,把焦点放在实现“异常安全性上”,因为让operator=具备“异常安全”往往自动获得“自我赋值安全”的回报。

<注意>“许多时候一群精心安排的语句就可以导出异常安全(以及自我赋值安全)的代码”,例如:

Widget&

Widget::operator=(const Widget& rhs)

{

Bitmap* pOrig = pb; //记住原先的pb

pb = new Bitmap(*rhs.pb); // pb指向*pb的一个副本

delete pOrig; //删除原先的pb

return *this;

}

它或许不是处理“自我赋值”的最高效办法,但它行得通。

版本4copy and swap技术,处理“自我赋值”的一个简单而通用的技术,并且是“异常安全的”(该版本的详细讨论可参考Exceptional C++

Widget&

Widget::operator=(const Widget& rhs)

{

Widget temp(rhs);

swap(temp);

return *this;

}

请记住

1、 确保当对象自我赋值时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap

2、 确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正常。

条款12:复制对象时勿忘其每一个成分

如果你为class添加一个成员变量,你必须同时修改copying函数(copy构造,copy赋值),也需要修改所有其他构造函数以及任何非标准形式的operator=

任何时候只要你承担起“为derived class撰写copying函数”的重责大任,必须很小心的复制其base class成分。那些成分往往是private的,所以你无法直接访问它们,你应该让derived classcopying函数调用相应的base class函数。

能否令某个copying函数调用另一个copying函数?不能!

copy assignment操作符调用copy构造函数是不合理的,因为这就像试图构造一个已经存在的对象。

copy构造函数调用copy赋值操作符同样无意义。构造函数用来初始化新对象,而赋值操作符只施加于已初始化对象身上。

使用private的函数init()来消除重复代码。

请记住:

1、 copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

2、 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics