《Effective C++》读书笔记(三) 构造/析构/赋值运算 (第二部分)
2013-09-16 22:39
381 查看
构造/析构/赋值运算
Constructors,Destructors,and Assignment Operators
条款09:绝不在构造和析构过程中调用virtual函数
Never call virtual functions during construction or destruction.
不要在构造函数和析构函数期间调用virtual函数,因为这样的调用不会带来预想的结果。当有个class继承体系,有base class和derived class存在时,由于base class构造函数的执行更早于derived class构造函数,所以当base class构造函数执行时,derived class的成员变量尚未初始化。如果此时在derived class构造函数中调用virtual函数,就很容易引起“要求使用对象内部尚未初始化的成分”。同理,对应的析构函数也是如此。
更根本的原因是:在derived class对象的base class构造期间,对象的类型是base class而不是derived class.不只virtual函数会被编译器解析至base class,若使用运行阶段类型识别,也会把对象视为base class类型。
侦测“构造函数和析构函数运行期间是否调用virtual函数”并不总是想象般轻松,唯一能避免此问题的做法是:确定构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们调用的所有函数也都服从同一约束。有个方案可以解决这个问题,参照下面代码例子:
这种做法是在class Transaction内将logTransaction函数改为non-virtual,然后要求derived class构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可安全地调用non-virtual logTransaction函数。
☆在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
条款10:令operator= 返回一个reference to *this
Have assignment operators return a reference to *this
虽然这只是个协议,并无强制性,如果不遵循它,代码一样可以通过编译。然而这份协议被所有内置类型和标准程序库提供的类型共同遵守。如果没有什么非常好的理由去标新立异,还是大众化合适些。
☆令赋值(assignment)操作符返回一个reference to *this
条款11:在operator= 中处理自我赋值
Handle assignment to self in operator=
“自我赋值”发生在对象被赋值给自己的时候。比如:
也有些不太明显或者潜在的自我赋值,一个以上的方法指涉某个相同对象,就很容易带来,比如:
a[i]=a[j]; 和 *px=*py; 都是潜在的自我赋值
假设建立一个class用来保存一个指针指向一块动态分配的Bitmap:
要阻止自我赋值,一个传统的方法是借用operator= 最前面的一个“证同测试”(identity test),达到目的:
传统做法有些缺陷,看起来还可以。因为它不具备异常安全性以及自我赋值安全性。第二种方法是不去管“自我赋值”,而是把注意力集中在实现“异常安全性”上:
第三种方法是利用copy-and-swap技术,但对于时间和空间上消耗比较高。因为copy-and-swap要对每一个即将被改动的对象做出一个temp副本
☆确保当对象自我赋值时operator= 有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
☆确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
条款12:复制对象时勿忘其每一个成分
Copy all parts of an object.
设计良好之面向对象系统(OO-systems)会将对象的内部封装起来,只留两个函数负责对象复制,那便是带着适当名称的copy构造函数和copy assignment操作符。可以把它们统称为copying函数。
需要注意的是,如果为class添加一个成员变量,也必须同时修改所有的copy构造函数和所有copy assignment操作符。
如果发生了继承,就更加要注意“为derived class撰写copying函数”的重要大任。必须很小心地复制其base class成分,要保证该有的部分都有。而且对于private部分,应该让derived class的copying函数调用相应的base class函数。
总而言之,当编写一个copying函数时,要确保复制所有local成员变量和调用所有base classes内的适当copying函数。
令copy assignment操作符调用copy构造函数是不合理的,就像试图构造一个已经存在的对象一样,非常荒谬。同理,令copy构造函数调用copy assignment操作符同样没意义。构造函数用来初始化新对象,assignment操作符只施行于已初始化对象身上。
如果发现copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。
☆Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。
☆不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用
参考文献:
《Effective C++》3rd Scott Meyers著,侯捷译
Constructors,Destructors,and Assignment Operators
条款09:绝不在构造和析构过程中调用virtual函数
Never call virtual functions during construction or destruction.
不要在构造函数和析构函数期间调用virtual函数,因为这样的调用不会带来预想的结果。当有个class继承体系,有base class和derived class存在时,由于base class构造函数的执行更早于derived class构造函数,所以当base class构造函数执行时,derived class的成员变量尚未初始化。如果此时在derived class构造函数中调用virtual函数,就很容易引起“要求使用对象内部尚未初始化的成分”。同理,对应的析构函数也是如此。
更根本的原因是:在derived class对象的base class构造期间,对象的类型是base class而不是derived class.不只virtual函数会被编译器解析至base class,若使用运行阶段类型识别,也会把对象视为base class类型。
侦测“构造函数和析构函数运行期间是否调用virtual函数”并不总是想象般轻松,唯一能避免此问题的做法是:确定构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们调用的所有函数也都服从同一约束。有个方案可以解决这个问题,参照下面代码例子:
class Transaction { public: explicit Transaction(const std::string& logInfo); void logTransaction(const std::string& logInfo)const; //non-virtual函数 ... }; Transaction::Transaction(const std::string& logInfo) { ... logTransaction(logInfo); //non-virtual调用 } class BuyTransaction: public Transaction { public: BuyTransaction(parameters) :Transaction(createLogString( parameters )) {...} //将log信息传给base class构造函数 ... private: static std::string createLogString( parameters ); };
这种做法是在class Transaction内将logTransaction函数改为non-virtual,然后要求derived class构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可安全地调用non-virtual logTransaction函数。
☆在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)
条款10:令operator= 返回一个reference to *this
Have assignment operators return a reference to *this
虽然这只是个协议,并无强制性,如果不遵循它,代码一样可以通过编译。然而这份协议被所有内置类型和标准程序库提供的类型共同遵守。如果没有什么非常好的理由去标新立异,还是大众化合适些。
☆令赋值(assignment)操作符返回一个reference to *this
条款11:在operator= 中处理自我赋值
Handle assignment to self in operator=
“自我赋值”发生在对象被赋值给自己的时候。比如:
class Widget {...}; Widget w; ... w=w; //赋值给自己
也有些不太明显或者潜在的自我赋值,一个以上的方法指涉某个相同对象,就很容易带来,比如:
a[i]=a[j]; 和 *px=*py; 都是潜在的自我赋值
假设建立一个class用来保存一个指针指向一块动态分配的Bitmap:
class Bitmap {...}; class Widget { ... private: Bitmap* pb; };
要阻止自我赋值,一个传统的方法是借用operator= 最前面的一个“证同测试”(identity test),达到目的:
Widget& Widget::operator=(const Widget& rhs) { if (this==&rhs) return *this; delete pb; pb=new Bitmap(*rhs.pb); return *this; }
传统做法有些缺陷,看起来还可以。因为它不具备异常安全性以及自我赋值安全性。第二种方法是不去管“自我赋值”,而是把注意力集中在实现“异常安全性”上:
Widget& Widget::operator=(const Widget& rhs) { Bitmap* pOrig=pb; pb=new Bitmap(*rhs.pb); delete pOrig; return *this; }
第三种方法是利用copy-and-swap技术,但对于时间和空间上消耗比较高。因为copy-and-swap要对每一个即将被改动的对象做出一个temp副本
class Widget { ... void swap(Widget& rhs); ... }; Widget& Widget::operator=(const Widget& rhs) { Widget temp(rhs); swap(temp); return *this; }
☆确保当对象自我赋值时operator= 有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。
☆确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。
条款12:复制对象时勿忘其每一个成分
Copy all parts of an object.
设计良好之面向对象系统(OO-systems)会将对象的内部封装起来,只留两个函数负责对象复制,那便是带着适当名称的copy构造函数和copy assignment操作符。可以把它们统称为copying函数。
需要注意的是,如果为class添加一个成员变量,也必须同时修改所有的copy构造函数和所有copy assignment操作符。
如果发生了继承,就更加要注意“为derived class撰写copying函数”的重要大任。必须很小心地复制其base class成分,要保证该有的部分都有。而且对于private部分,应该让derived class的copying函数调用相应的base class函数。
总而言之,当编写一个copying函数时,要确保复制所有local成员变量和调用所有base classes内的适当copying函数。
令copy assignment操作符调用copy构造函数是不合理的,就像试图构造一个已经存在的对象一样,非常荒谬。同理,令copy构造函数调用copy assignment操作符同样没意义。构造函数用来初始化新对象,assignment操作符只施行于已初始化对象身上。
如果发现copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员函数给两者调用。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。
☆Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。
☆不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用
参考文献:
《Effective C++》3rd Scott Meyers著,侯捷译
相关文章推荐
- Effective C++ 笔记 第二部分 构造/析构/赋值运算
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之四)
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之二)
- 《Effective C++》读书笔记(二) 构造/析构/赋值运算 (第一部分)
- Effective C++读书笔记 第二部分 构造/析构/赋值运算
- 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
- 《Effective C++》读书笔记(六) 设计与声明(第二部分)
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之一)
- 《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记
- 《Effective C++》 读书笔记之二 构造/析构/赋值运算
- 《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记
- effective c++ 第二部分(构造析构赋值运算)
- 【读书笔记】Effective C++-2 构造/析构/赋值运算(之三)
- C++ Primer 4th 读书笔记(第二部分)
- 《CLR.via.C#第三版》第二部分第12章节 泛型 读书笔记(六)
- 《CLR.via.C#第三版》第二部分第13章节 接口 读书笔记(七)
- 读书笔记_Effective_C++_条款三十一:将文件间的编译依存关系降至最低(第二部分)
- 《Pro Ogre 3D Programming》 读书笔记 之 第三章 设计概要 第二部分 (转)
- MFC深入浅出读书笔记第二部分1
- 代码大全第二版读书笔记 第二部分-创建高质量的代码 九、伪代码编程过程