原则9:绝不在构造和析构过程中调用virtual函数

这是《Effective C++》中的第9条原则。
简单的来说,如果你在父类的构造函数中调用了虚拟函数,那么子类的成员就会始终处于未初始化的状态,这样对象的子类成分就会出现不可预知的行为,这是非常危险的。
那么这是为什么呢?在继承体系当中,你声明了一个子类对象,那么这个子类对象其实是很复杂的,它包含了子类所有父类的成分。而这些成分是要一层一层的进行初始化的,其顺序是按照类的继承层次从上而下进行的,即从最远的那个父类到最近的那个父类,然后是本类的初始化。而C++的机制又是只有在父类成分初始化完毕以后才去处理子类成分的初始化工作,换句话说,如果父类的成分没有初始化完毕,它压根就不会去管子类的初始化工作。因为你在父类的构造函数中调用了虚拟函数,而这个虚拟函数一般在父类中是不进行实现的。鄙人以为,一个函数之所以被调用肯定是因为这个函数是有一定的功能实现的,要不你调用它干吗?我想C++的构造函数也是这么想的。但是,你现在非要在本来用于初始化的构造函数中去调用一个没法初始化virtual函数,C++就认为这个对象的父类成分还没有初始化完毕,现在不能去初始化子类成分。所以,即使你现在声明了一个子类对象,子类中的成分还是没有得到初始化,所以就会出现不可预知的后果。
C++对未定位的成员变量是采取无视的态度的,因为还没轮到你,你给我一边凉快去。在构造函数期间无视,在析构函数期间也是无视。而析构的顺序又是先子类再父类,因为对象的子类成分被无视,只有父类成分被析构,所以那些未定义的成员变量自始至终都是未定义的,你也不知道它们最终会怎样。
为了证实这一点请看下面的例子:


原则9:绝不在构造和析构过程中调用virtual函数_第1张图片

原则9:绝不在构造和析构过程中调用virtual函数_第2张图片

运行结果是这样的:



看到这个结果,我不得不说现在的编译器已经很智能了,它直接把它拦下来了。
在介绍与本原则有关的内容时,作者举了一个应用场景。那就是当类中有多个不同版本的构造函数,它们的共同的初始化代码都统一放到一个初始化成员函数里面了,并且这个初始化成员函数调用了一个virtual成员函数,并且这个virtual成员函数会有一定的实现代码。当你建立子类对象时却调用了错误的virtual成员函数。在此作者并没有详细地解释是为什么,但他给出了一种解决办法。那就是在子类的构造函数的初始化成员列表中调用父类的构造函数去初始化对象中父类的部分,当然了,这时父类中的那个virtual函数你要改成非virtual函数了。在这里作者使用了一个技巧,他不是在成员初始化列表直接把父类成分所需的东西直接给它,而是通过一个辅助函数返回一个值给父类进行初始化,这样写比较方便也比较可读。而且,这个辅助函数的是一个static类型。static类型的成员函数是静态成员函数,它的类的对象实例化之前就已经被实例化,换句话说它跟类中其他的成员不发生关系。另外,子类对象的父类成分是在子类成分之前先被实例化,而在子类对象实例化的中间也就是父类成分正在实例化,还没轮到子类成分实例化之前,子类中的成员函数啥的是未定义的,也就工作不了。而此时static成员函数却能工作,这有助于对象中父类成分的实例化,所以把此辅助函数设置为static类型。

你可能感兴趣的:(原则9:绝不在构造和析构过程中调用virtual函数)