首先摘取一段来自《Effective C++》的解释:
派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。 同样,进入基类析构函数时,对象也是基类类型。
再来看一段经验性的总结:
- 从语法上讲,调用完全没有问题。
- 但是从效果上看,往往不能达到需要的目的。
接着写段代码来探究一下究竟能否达到需要的目的,在此之前,我们先搞清楚目的是什么?
既然在子类中的构造和析构函数中都调用虚函数,那么我们肯定是希望子类中的构造和析构函数都能调用自己的虚函数版本,例如在下例中,我们的目的是:
- A 在构造时先调用 Base 类构造函数(Base 类的构造函数中调用了虚函数),再调用 A 的构造函数(A 类的构造函数中调用了虚函数)
- A 在析构时先调用 A 类析构函数(A 类的析构函数中调用了虚函数),再调用 Base 类的析构函数(Base 类的析构函数中调用了虚函数)
我们要探究的是:
- Base 类的构造函数中调用 Base 版本的虚函数,A 类的构造函数中调用 A 版本的虚函数
- A 类的析构函数中调用 A 版本的虚函数,Base 类的析构函数中调用 Base 版本的虚函数
此探究目的符合:派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。 同样,进入基类析构函数时,对象也是基类类型。
接下来我们写段代码验证其正确与否:
#include<iostream>
using namespace std;
class Base
{
public:
Base()
{
Function();
cout << "Base constructor" << endl;
}
virtual void Function()
{
cout << "Base::Fuction" << endl;
}
virtual ~Base()
{
Function();
cout << "Base destructor" << endl;
}
};
class A : public Base
{
public:
A()
{
Function();
cout << "A constructor" << endl;
}
virtual void Function()
{
cout << "A::Function" << endl;
}
virtual ~A()
{
Function();
cout << "A destructor" << endl;
}
};
int main()
{
Base* a = new Base;
delete a;
cout << "-------------------------" <<endl;
Base* b = new A;//语句1
delete b;
}
以上代码输出为:
结果符合:
- Base 类的构造函数中调用 Base 版本的虚函数,A 类的构造函数中调用 A 版本的虚函数
- A 类的析构函数中调用 A 版本的虚函数,Base 类的析构函数中调用 Base 版本的虚函数
那么,为什么说:
- 但是从效果上看,往往不能达到需要的目的?
因为,类设计者可能希望在基类指针指向子类对象时,通过该基类指针调用的虚函数版本应该是子类的虚函数版本。但是,很明显我们看到:
- 在进入基类构造函数时,调用了基类版本的虚函数
- 在进入基类虚构函数时,调用了基类版本的虚函数
这可能与预期不相符合。
毕竟,如果我们需要这样的结果的话,为什么不在一开始就把该虚函数声明为普通函数呢?这样可以达到同样的效果:
- 将该虚函数声明为普通函数的话,同样达到了上述实验一样的效果
这便是所谓的:
- 但是从效果上看,往往不能达到需要的目的