C++之构造函数和析构函数中不要调用virtual函数(9)---《Effective C++》

来源:互联网 发布:根据package.json安装 编辑:程序博客网 时间:2024/05/16 13:39

条款9:绝不要在构造函数和析构函数中调用virtual函数

为什么不要在构造函数和析构函数中调用virtual函数呢?下面请大家带着上述问题来看如下代码:

class Base{public:    Base();        virtual void hello();        ...    }private:    int x;    int y;};Base::Base(){    ...    hello();}class ABase:public Base{public:    virtual void hello();//这儿可以声明为virtual,也可以不用声明为virtual,因为在基类中已经声明为virtual,那么子类中相应的所有函数都就变味virtual    ...};class BBase::public Base{public:    virtual void hello();    ...}ABase a;

试想上面代码运行时候会发生什么问题?ABase的构造函数被调用,ABase的基类Base构造函数一定更早调用,即ABase中从Base中继承的x,y变量通过Base的构造函数被初始化,ABase中的z变量尚未被初始化,此时好戏上演。
1)Base类的构造函数中调用的虚函数hello()函数是基类Base的,还是子类ABase的呢?答案当然是基类Base的,因为在基类Base的构造函数执行期间,virtual函数绝对不会下降到子类ABase阶层,如果这样比较枯燥的话,我们可以反向解释:2)基类Base的构造函数执行期间,子类ABase中的成员变量z并没有被初始化,如果在基类Base的构造函数中调用的virtual函数已经下降到了子类ABase阶层,那么Base中调用的这个virtual函数一定要取取用子类ABase中的成员变量,而这些成员变量并没有被初始化,别挣扎了,C++不允许这样,可以这样理解:在Base类构造期间,virtual函数不是virutal函数,3)其实最主要的原因是:在子类ABase对象的基类构造函数执行期间,对象的类型是基类Base的,而不是子类ABase的,不止virtual函数会被解至基类Base中,也会把对象视为基类Base类型,在本例中,当子类ABase的构造函数执行起来时,一定先调用基类Base的构造函数来初始化子类ABase中的基类成分,这时候对象的类型是基类Base的,同时这时子类ABase中的专属成分如z尚未被初始化,对象在子类构造函数(即子类中的基类构造函数执行结束后)开始执行之前一定不会成为一个子类对象。
同样对着适用于析构函数,C++的析构函数先执行子类的析构函数,后执行基类的析构函数,执行和构造函数执行的顺序刚好相反,一旦子类析构函数开始执行,对象中的子类成员变量呈现未定义值,C++视它们不存在,进入基类析构函数之后就成为一个基类对象,C++的任何部分包括virtual函数,dynamic_casts也同样这样看待它。
**

需要注意的是,有的编译器对这种情况报错,而有的并不报错,很坑的!

**

由于这样并没有执行virtual函数,达到相应的效果,因此,C++明确规定不能在构造函数和析构函数中调用virtual函数。

其他方法可以解决这个问题,其中之一是在基类Base中virtual函数hello声明为non-virtual,然后要求子类ABase构造函数传递必要的信息给Base构造函数,因为基类Base中调用了Base的成员函数hello,不可能下降至子类ABase中,因此安全,为了双重安全,我们也可以选择利用static函数提供数据给基类Base数据,这样在子类ABase中一定不会存在未被初始化的数据,而后这个构造函数就可以安全的调用non-virtual函数hello,如:

class Base{public:    Base(int x,int y);    void hello();//现在是个non-virtual函数    ...private:    int x;    int y;};Base::Base(int x,int y):x(x),y(y){    hello();//现在是个non-virtual调用}class ABase:public Base{public:    ABase(params):Base(createParams(params)){        ...    }    ...private:    static *** createParams(parameters);};

这种方式是怎样的呢?你无法使用virtual函数从基类Base向下调用,在构造期间,你可以籍由“令子类将必要的构造信息向上传递值基类构造函数”替换加以弥补,这里令createParams函数为static,也就不可能意外指向“初期未成熟之子类ABase对象中尚未初始化的成员变量”。

总结如下:在构造函数中不要调用virtual函数,因为这类调用从不下降至子类derived class这层。

阅读全文
0 0
原创粉丝点击