条款09:绝不在构造和析构过程中调用virtual函数
例子:
#include#include using namespace std;class Base{public: Base(){ test(); } virtual void test(){ cout << "Base test" << endl; }};class Derived :public Base{public: Derived(){ cout << "drived 构造" << endl; } virtual void test(){ cout << "Derived test" << endl; }};int main(){ Derived der; system("pause"); return 0;}
运行结果:
从上述结果可以看到,基类构造期间virtual函数绝对不会下降到派生类型阶层,也就是说在基类构造期间virtual函数并不是virtual的。由于base class构造函数的执行更早于derived class构造函数,当base class构造函数执行时derived class的成员变量尚未初始化。如果此期间调用的virtual函数下降至derived class阶层,要知道derived class的函数几乎必然取用local成员变量,而那些成员变量尚未初始化。 “要求使用对象内部尚未初始化的成分”是危险的代名词,所以C++不让你走这条路。
更根本的原因:在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至(resolve to)base class,若使用运行期类型信息(runtime type information,例如dynamic_cast(见条款27)和typeid),也会把对象视为base class类型。
请记住:
- 在构造函数和析构期间不要调用虚函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。
条款10:令operator= 返回一个referenceto *this
对于赋值操作符,我们常常要达到这种类似效果,即连续赋值:
int x, y, z;x = y = z = 15;
为了实现“连锁赋值”,赋值操作符必须返回一个“引用”指向操作符的左侧实参。
所有内置类型和标准程序库提供的类型如string,vector,complex或即将提供的类型共同遵守。
请记住:
- 令赋值操作符返回一个reference to *this。
条款11:在operateor=中“自我赋值”
“自我赋值”指对象被赋值给自己。
class Widget{ ... };Widget w;...w = w;//自我赋值a[i] = a[j]; //如果i等于j,这个就是自我赋值*px=*py //当px和py指向同一个对象时,就是自我赋值class Base{ ... };class Derived:public Base{ ... };void doSomething(const Base& rb,Derived* pd) //rb和*pd有可能是同一对象例子:
class Bitmap{ ..... };class Wdiget{public: ... Wdiget& operator=(const Wdiget& rhs){ delete pb; pb = new Bitmap(*rhs.pb); return *this; }private: Bitmap* pb;};
上述例子的问题是rhs与*this有可能是同一个对象,那么当delete p以后,就包含一个空的对象,因此最终的p指向一个被删除的对象。为解决这个问题我们可以在delete之前首先做判同操作,如下:
Wdiget& operator=(const Wdiget& rhs){ if (&rhs == this) return *this; delete pb; pb = new Bitmap(*rhs.pb); return *this;}
这样做可行,但如果在new Bitmap的时候出现异常,Widget最终会持有一个指针指向一块被删除的Bitmap,这样的指针有害。下面是另外的一种解决办法:
Wdiget& Wdiget::operator=(const Wdiget& rhs){ Bitmap* pOrig = pb; pb = new Bitmap(*rhs.pb); delete pOrig; return *this;};
这样当如果new Bitmap出现异常,pb保持原状。还提到一种copy and wap的技术,在条款29会详细说明。
请记住:
- 确保当对象进行自我赋值的时候有良好的行为,其中包括来源对象和目标对象的比较,设计良好的赋值顺序,以及copy and swap技术。
- 要确保当一个函数操作对个对象,并且多个对象可能是同一个对象的时候行为也是准确的。
条款12:复制对象时勿忘其每一个成分
1、条款5中观察到编译器在必要时会为我们的类提供拷贝构造函数和拷贝赋值函数,并说明这些“编译器生成版”的行为:将被拷对象的所有成员变量都做一份拷贝。但有时候我们需要编写自己的拷贝构造函数和拷贝赋值函数,我们应确保对每一个成员进行拷贝。
2、你拒绝编译器为你写出copying函数,如果你的代码不完全,编译器也不会告诉你。如果你为类添加一个成员变量,你必须同时修改copying函数(所有的构造函数,拷贝构造函数以及拷贝赋值操作符)。
3、在派生类的构造函数,拷贝构造函数和拷贝赋值操作符中应当显示调用基类相对应的函数。
4、当你编写一个copying函数,请确保:
(1)复制所有local成员变量,
(2)调用所有基类内的适当copying函数。
但是,我们不该令拷贝赋值操作符调用拷贝构造函数,也不该令拷贝构造函数调用拷贝赋值操作符。
请记住:
- Copying函数应该确保复制“对象内的所有成员变量”及“所有基类成分”;
- 不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。
版权声明:本文为博主原创文章,未经博主允许不得转载。