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時,如果不定義成虛函數,派生類中派生的那部分無法析構。