C++学习笔记-----不要在构造函数和析构函数中调用虚函数

来源:互联网 发布:淘宝摄影培训班 编辑:程序博客网 时间:2024/05/17 03:15

考虑下面的程序:

#include <iostream>using namespace std;class Base{public:Base() { cout << "Base Construct" << endl; func(); }~Base() { cout << "Base Destroy" << endl; }void toG() { g(); }virtual void func() { cout << "Base virtual function" << endl; }private:virtual void g() { cout << "Base Private virtual" << endl; }};class Derive : public Base{public:Derive() : Base() { cout << "Derive Construct" << endl;  func(); }~Derive() { cout << "Derive Destroy" << endl; }void func() { cout << "Derive virtual function" << endl; }private:void g() { cout << "Derive Private virtual" << endl; }};int main(){Derive d;cout << endl;d.toG();cout << endl;cin.get();return 0;}


如你所见,我们定义了一个基类Base,又定义了一个派生类Derive并以public的形式继承它。

在构造函数中分别输出不同的字符串常量然后调用虚函数func()。

另外在基类中又存在一个toG()函数用来调用函数内部的虚函数g(),通常这里的g函数应该定义在私有成员作用域中,为了实现用同一个基类接口调用不用的派生类虚函数的目的。

让我们来看一下程序的运行结果:


发现什么不对的地方了吗,如果没有,我们一步步分析:

Derive d;
首先像往常一样,定义了一个派生类的实例化对象,构造的顺序是从基类到派生类。先调用基类的构造函数输出“Base Construct”,又调用了基类的func()函数输出了“Base virtual function“,到这里基类部分构造完成,开始进入派生类的构造阶段,输出”Derive Construct“,又调用了派生类的func()函数输出了”Derive virtual function"。

d.toG();
我们用派生类的对象调用基类的接口函数toG(),在函数内部调用了派生类的g()函数输出”Derive Private virtual“。

比较上面的结果,不难发现,同样是在基类的作用域下,func()函数调用的是基类的,g()函数却调用的是派生类的。

这正是问题所在,想一想,上述哪种调用结果是符合预期的,为什么会出现这样的不一致?

涉及到虚函数调用的问题基本上都可以用虚函数表来分析解决,在定义派生类对象的时候,因为func和g两个函数都是虚函数且在派生类中都重新定义了,所以虚函数表中函数的地址都会被覆盖成派生类的虚函数的地址,也就是说只要我们用派生类的对象调用这两个函数,会在虚函数表中寻找最佳匹配的函数获得它的函数地址并调用它,不管作用域是在基类还是在派生类,都会调用派生类的相应函数。

然而func()的调用好像并非如此,这就是为什么说不要在构造函数中调用虚函数的原因。

至于为什么会出现这样的结果,我们知道,在派生类对象的构造阶段是先构造其基类部分的,也就是可以理解成在构造基类的时候派生类的部分还不存在,试想,我们又怎么能去调用一个并不存在的东西的子成员呢?编译器也正是假设在构造阶段派生类对象还不存在,所以不会去调用派生类的虚函数。


归结一点,不要在构造函数中调用虚函数,更准确的说法是不要在基类的构造函数中调用虚函数,因为编译器调用的永远都是属于基类的那个虚函数而不是派生类的。

同样的原因可以解释析构函数,因为析构函数的调用时从派生类到基类的,也就是说先销毁派生类独立的那部分,在向上销毁它的基类。那么如果在基类的析构函数中调用了虚函数,此时派生类部分已经被销毁了,又不存在了,那调用的虚函数永远都是基类的那个。



0 0
原创粉丝点击