Q:类的构造函数是否可以是虚函数?
A:不可以。
首先,设置后编译出错,语法上c++并不允许把类的构造函数声明为虚函数;
其次,抛开语法层面,类对象构造的时候是按照继承层次从上至下开始构造。创建一个类对象的时候是先分配整个类需要的存储空间,然后开始执行构造函数,举个简单的例子:
class A { public: A() {} virtual void Print() { printf("this is A.\n"); } }; class B : public A { public: B() {} virtual void Print() { printf("this is B.\n"); } }; int main(int argc, char** argv) { B b; return 0; }
对象b构造的时候会先执行父类A的构造函数,父类A构造函数执行的时候并不知道继承关系是如何的,换言之父类A并不知道类B的存在,此时若A的构造函数为虚函数,也只会调用A自身的构造函数,虚函数的多态性并不会体现。
最后,我们知道类的多态性是通过虚表指针来实现的,虚表指针在构造函数调用的时候初始化,而我们把构造函数设置为虚函数,也就是说这个时候要实现多态性必须要有已经初始化的虚表指针,然而虚表指针这时候并未初始化,这就形成了一个悖论。另外有一点就是初始化父类部分虚表指针指向的是父类自己的虚表,父类自己的虚表只会存放父类自己的虚函数的指针,表里并没有子类虚函数的指针,所以也不可能形成多态性。
Q:构造函数是否可以调用虚函数?
A:可以。但是形成不了多态性。
举个例子:
#include <stdio.h> #include <typeinfo> using namespace std; class A; typedef void(*Fun)(A*); class A { private: int m_a; public: A(int a) : m_a(a) { //printf("%s\n", typeid(*this).name()); //printf("this: %p\n", this); printf("vptr: %p\n", *(int*)this); printf("%p\n", (int*)*((int*)(*(int*)this) + 1)); printf("%p\n", (int*)*((int*)(*(int*)this) + 2)); ((Fun)*((int*)(*(int*)this) + 1))(this); ((Fun)*((int*)(*(int*)this) + 2))(this); printf("\n"); } virtual ~A() { }; virtual void show() const { printf("show a: %d\n", m_a); } virtual void disp() const { printf("disp a: %d\n", m_a); } }; class B : public A { private: int m_b; public: B(int a, int b) : m_b(b), A(a) { //printf("this: %p\n", this); printf("vptr: %p\n", *(int*)this); printf("%p\n", (int*)*((int*)(*(int*)this) + 1)); printf("%p\n", (int*)*((int*)(*(int*)this) + 2)); ((Fun)*((int*)(*(int*)this) + 1))(this); ((Fun)*((int*)(*(int*)this) + 2))(this); } ~B() { } void show() const { printf("show b: %d\n", m_b); } void disp() const { printf("disp b: %d\n", m_b); } }; int _tmain(int argc, _TCHAR* argv[]) { A* pob3 = new B(100, 200); delete pob3; getchar(); return 0; }运行结果如下:
vptr: 00C97860 00C91131 00C910C3 show a: 100 disp a: 100 vptr: 00C9795C 00C9107D 00C9115E show b: 200 disp b: 200
从上面可以看出,构造父类的时候父类初始化的虚表指针是指向父类自己的虚表的,而后在构造到子类的时候才会把虚表改为子类的虚表,所以构造函数调用虚函数不会出错,但不会有多态性。
Q:析构函数是否可以调用虚函数?
A:可以,但同样不会具有多态性。
以下是例子分析:
#include <stdio.h> #include <typeinfo> using namespace std; class A; typedef void(*Fun)(A*); class A { private: int m_a; public: A(int a) : m_a(a) { } virtual ~A() { printf("%s\n", typeid(*this).name()); printf("this: %p\n", this); printf("vptr: %p\n", *(int*)this); ((Fun)*((int*)(*(int*)this) + 1))(this); ((Fun)*((int*)(*(int*)this) + 2))(this); printf("\n"); }; virtual void show() const { printf("show a: %d\n", m_a); } virtual void disp() const { printf("disp a: %d\n", m_a); } void T() { show(); disp(); } }; class B : public A { private: int m_b; public: B(int a, int b) : m_b(b), A(a) { } ~B() { printf("%s\n", typeid(*this).name()); printf("this: %p\n", this); printf("vptr: %p\n", *(int*)this); ((Fun)*((int*)(*(int*)this) + 1))(this); ((Fun)*((int*)(*(int*)this) + 2))(this); printf("\n"); } void show() const { printf("show b: %d\n", m_b); } void disp() const { printf("disp b: %d\n", m_b); } }; int _tmain(int argc, _TCHAR* argv[]) { A* pob3 = new B(100, 200); delete pob3; getchar(); return 0; }
class B this: 003CE180 vptr: 00EF78B0 show b: 200 disp b: 200 class A this: 003CE180 vptr: 00EF7860 show a: 100 disp a: 100
(网友相关解释:编译器的做法是,析构子类完成后,恢复父类对象的的虚函数表,这时子类对象对应的父类对象的虚函数表,已经是父类的虚函数表,此时调用虚函数,就不在和正常虚函数调用一样了,父类对象只能调用自己的虚函数,非虚函数调用方式并无不同。)
Q:析构函数为什么要设为虚函数?
A:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。