c++類構造函數、析構函數與虛函數之間的那點小事

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章