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
从以上同样可以看出,在析构A的时候,虚表指针已经从指向类B的虚表变为指向类A的虚表了,所以并不能实现多态性。
(网友相关解释:编译器的做法是,析构子类完成后,恢复父类对象的的虚函数表,这时子类对象对应的父类对象的虚函数表,已经是父类的虚函数表,此时调用虚函数,就不在和正常虚函数调用一样了,父类对象只能调用自己的虚函数,非虚函数调用方式并无不同。)
Q:析构函数为什么要设为虚函数?
A:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。