1. 程式人生 > >虛擬函式的的原理及應用

虛擬函式的的原理及應用

1,虛擬函式的概念

虛擬函式是在類中被宣告為virtual的成員函式,當編譯器看到通過指標或引用呼叫此類函式時,對其執行晚繫結,即通過指標(或引用)指向的類的型別資訊來決定該函式是哪個類的。通常此類指標或引用都宣告為基類的,它可以指向基類或派生類的物件。


2,多型的概念

多型指同一個方法根據其所屬的不同物件可以有不同的行為。

(1)校長說放假的例子,不同的人有不同的行為。

(2)小王畢業後,賺錢,買了一輛寶馬開。



這種實現不科學





3,虛擬函式的實現機制(虛擬函式表和虛擬函式表指標)

一個類物件佔多少個位元組。


說到虛擬函式的實現方法,我們就不得不說到動態聯編(dynamic binding

)和靜態聯編(static binding)。靜態聯編意味著編譯器能夠直接將識別符號和儲存的實體地址聯絡在一起。每一個函式都有一個唯一的實體地址,當編譯器遇到一個函式呼叫時,它將用一個機械語言說明來替代函式呼叫,用來告訴CPU跳至這個函式的地址,然後對此函式進行操作。這個過程是在編譯過程中完成的(注:呼叫的函式在編譯時必須能夠確定),所以靜態聯編也叫前期聯編(early binding)。但是,如果使用哪個函式不能在編譯時確定,則需要採用動態聯編的方式,在程式執行時在呼叫函式,所以動態聯編也叫後期聯編(late binding)。

在C++繼承多型中,如若要在派生類中重新定義基類的方法,則要把它宣告為虛擬函式,並且用指標或者引用去呼叫它的方法,實現動態聯編,否則編譯器預設的將是靜態聯編。

class A

{

public:

    virtual void f()   { cout << "A’s f()" <<endl; } //f()被宣告為虛擬函式

    virtual void g()   { cout << "A’s g()"<< endl;} //g()被宣告為虛擬函式

};

 

class B : publicA

{   

public:

    void f()   { cout << "B’s f()" << endl; }

};

 

class C : publicA

{

public:

    void g()   { cout << "C’s g()" << endl; }

};

int main (void)

{

    A     *pa;

    B     b;

    C     c;

    pa = &b;

    pa -> f();

    pa -> g();

    pa = &c;

    pa -> f();

    pa -> g();

    return 0;

}


但是,呼叫一個虛擬函式比呼叫一個非虛擬函式的速度要慢一些,原因:首先,我們必須使用*__vptr去獲得合適的virtual table,然後通過這張virtual table的索引才可以找到正確的呼叫函式,只有這樣我們才可以呼叫這個函式。使用虛擬函式在記憶體方面也有一定的成本,即每個物件都講增大,增大量為儲存地址的空間。

 

每個類都有一個自己的vtable,每個物件都有自己的*__vptr(繼承的)

C++<span times="" new="" ';="" mso-hansi-font-family:="" 'times="" "="">的編譯器是保證虛擬函式表的指標存在於物件例項中最前面的位置

多重繼承的虛擬函式結構,看下面這篇部落格

http://haoel.blog.51cto.com/313033/124595/

 

4,為什麼要用虛解構函式

輸出結果是:
Do something in class ClxDerived!

這個結果是很危險的,因為ClxDerived的解構函式沒有被呼叫到。


 答案:這樣做是為了當用一個基類的指標刪除一個派生類的物件時,派生類的解構函式會被呼叫。

注意:當然,並不是要把所有類的解構函式都寫成虛擬函式。
因為當類裡面有虛擬函式的時候,編譯器會給類新增一個虛擬函式表,裡面來存放虛擬函式指標,這樣就會增加類的儲存空間。所以,只有當一個類被用來作為基類的時候,才把解構函式寫成虛擬函式。

 

5,為什麼建構函式不能是虛擬函式

當一個建構函式被呼叫時,它做的首要的事情之一是初始化它的V P T R

所以它使用的V P T R必須是對於這個類的V TA B L E。而且,只要它是最後的建構函式呼叫,那麼在這個物件的生命期內, V P T R將保持被初始化為指向這個V TA B L E,但如果接著還有一個更晚派生的建構函式被呼叫,這個建構函式又將設定V P T R指向它的 V TA B L E,等.直到最後的建構函式結束。V P T R的狀態是由被最後呼叫的建構函式確定的。這就是為什麼建構函式呼叫是從基類到更加派生類順序的另一個理由。

(1)虛擬函式是對應一個vtable的,虛擬函式的呼叫是通過虛擬函式表的指標來呼叫的。而虛擬函式表的指標是在建構函式中生成的,可是物件還沒有例項化,虛擬函式表的指標還沒有指向對應類的虛擬函式表,所以也無法呼叫虛的建構函式。

(2)虛擬函式的作用在於通過父類的指標或者引用來呼叫它的時候能夠變成呼叫子類的那個成員函式。而建構函式是在建立物件是自動呼叫的,不可能通過父類的指標或者引用去呼叫,因此也就規定建構函式不能是虛擬函式。

 

6,純虛擬函式

 class <類名>
    {

       virtual <型別><函式名>(<引數表>)=0;
       …

   };

帶有純虛擬函式的類稱為抽象類,不能被例項化。

 

我是一個抽象類,不要把我例項化,純虛擬函式的作用是用來規範派生類的行為,實際上就是所謂的“介面”,它告訴使用者,我的派生類都會有這個函式。

 

以下載模組為例子

下載模組是一個單獨的模組

下載中有三種狀態,下載失敗,下載中(進度更新),下載完成,需要將這三種狀態告訴UI層。

有三種方式:

(1)cocos2d-x中的觀察者模式

(2)在下載模組中定義一個ui層的指標,在初始化下載模組時,將ui層指標傳進來。

(3)寫一個純虛基類CDelegate,定義好三個介面,然後在下載模組中定義一個CDelegate的指標,哪個ui層用這個下載模組,哪個ui層就繼承這個CDelegate虛基類,然後在初始化下載模組時將自己的指標傳進來。這樣就可以達到下載模組的公用。

#include "stdafx.h"
#include <iostream>
using namespace std;
class CDelegate
{
public:
	virtual void OnDownSuccess()=0;
	virtual void OnDownFail()=0;
	virtual void OnDownProgress(double progress)=0;
};

class CDownLoad
{
public:
	CDownLoad(CDelegate* delegate){ m_delegate = delegate; }
	~CDownLoad(){}
public:
	void start()
	{ 
		cout<<"do down things"<<endl;
		OnEvent();
	}
	void OnEvent()
	{
		double process = 0.5;;
		m_delegate->OnDownFail();
		m_delegate->OnDownSuccess();
		m_delegate->OnDownProgress(process);
	}
private:
	CDelegate* m_delegate;
};

class UILayer1 : public CDelegate
{
public:
	virtual void OnDownSuccess(){ cout<<"UILayer1::OnDownSuccess()"<<endl; }
	virtual void OnDownFail(){ cout<<"UILayer1::OnDownFail()"<<endl; }
	virtual void OnDownProgress(double progress){ cout<<"UILayer1::OnDownProgress()"<<endl; }
public:
	void start_download()
	{
		CDownLoad*  down_load = new CDownLoad(this);
		down_load->start();
	}
};

class UILayer2 : public CDelegate
{
public:
	virtual void OnDownSuccess(){ cout<<"UILayer2::OnDownSuccess()"<<endl; }
	virtual void OnDownFail(){ cout<<"UILayer2::OnDownFail()"<<endl; }
	virtual void OnDownProgress(double progress){ cout<<"UILayer2::OnDownProgress()"<<endl; }
public:
	void start_download()
	{
		CDownLoad*  down_load = new CDownLoad(this);
		down_load->start();
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	UILayer1* layer1 = new UILayer1();
	layer1->start_download();

	UILayer2* layer2 = new UILayer2();
	layer2->start_download();
	system("pause");
	return 0;
}