關於virtual 一般有3種用法: virtual function, pure virtual function, 和 virtual inheritance. 今天就分別來講講這幾種virtual.
================ virtual function ================
virtual函數是在類裡邊的一種特殊的函數,至於具體的含義,相信大家都知道,我就不多說了。而virtual最基本的思想,就是OO語言中多態的體現。把函數從編譯時間的棒定,轉移到運行時的動態綁定。
virtual的好處很多,比如能使程式結構更加清晰,代碼的重複利用率更高, 但是也是有代價的。讓我們來看下代碼:
class Test {
...
virtual void VirtualFunc(void) { cout<<"Test1"<<endl; };
};
class Test2 : public Test {
...
virtual void VirtualFunc(void) { cout<<"Test2"<<endl; };
};
Test * pTest = new Test2();
pTest->VirtualFunc();
上面代碼的輸出結果是"Test2"。也就是說,雖然你是通過Test * 進行的調用,但真正執行的代碼是你建立的Test2的VirtualFunc. 這樣的效果是通過在編譯的時候建立virtual pointer: _vptr 和 virtual table: _vtbl 來實現的(大部分編譯器的實現是通過_vptr & _vtbl)。對於每個有virtual function的class,compiler建立一個_vtbl,用來記錄所有的virtual function的地址。同時在所有這個class的instance(執行個體)裡,都有一個_vptr,指向_vtbl。因此, pTest->VirtualFunc() 這段代碼等同於:
(*pTest->_vptr[X])()
其中X是VirtualFunc在_vtbl中對應的位置。(其實對於不同的compiler,都有不同的實現,不同的類階層,_vptr和_vtbl的數目也不一定相同。)
從上面的代碼考慮,virtual 和 non-virtual的class對比,在效能上有3個方面受到影響:
1. _vptr是在constructor中由compiler產生的程式碼隱性的初始化,佔用了一定的時間。
2. virtual function是通過指標間接調用的,比直接調用需要更多的時間,而且在有的情況下會導致CPU的指令緩衝失效,從而浪費更多的時間。
3. 由於virtual function是在運行時進行動態綁定的,所以無法進行inline.
對於第一條,如果是使用 virtual function,那是無可避免的,對於第2條,有通過顯式的調用virtual function來提高速度,代碼如下:
class Test2 : public Test {
...
virtual void VirtualFunc(void) { cout<<"Test2"<<endl; };
void VirtualFuncFast(void) { Test2::VirtualFunc(); };
};
由於Test2::VirtualFunc指定了調用函數的版本,所以是在編譯時間候就綁定了,免去了指標的間接調用過程,而且 VirtualFuncFast本身也是可以inline的。當然了,這樣做也是有壞處的,就是VirtualFuncFast本身是Test2的函數,而不是Test的,所以不能通過Test的指標掉用。因此只有在確定是Test2的情況下,通過static_cast才能掉用 VirtualFuncFast.
同理,在一個virtual function裡調用另外一個virtual function的時候,使用顯試的調用也能提高一定的速度,比如:
class Test2 : public Test {
...
virtual void VirtualFunc(void) { cout<<"Test2"<<endl; };
virtual void VirtualFunc2(void) { Test2::VirtualFunc(); };
};
對於第3種情況,我們可以通過用Template代替Virtual Function來獲得效能上的提高,舉個簡單的例子:
========== virtual 實現 ==========
class Base{
public:
virtual void VirtualFunc(void);
};
class Derive1 : public Base{
public:
virtual void VirtualFunc(void);
};
class Derive2 : public Base{
public:
virtual void VirtualFunc(void);
};
class SomeClass {
public:
void test(Base * pBase) { pBase->VirtualFunc(); };
};
// 用法
Test * pTest1 = new Derive1();
Test * pTest2 = new Derive2();
SomeClass Temp1.test(pTest1);
SomeClass Temp2.test(pTest2);
========== 對應的 Template 實現 ==========
class D1{
public:
// inline here
void VirtualFunc(void) {};
};
class D2{
public:
// inline here
void VirtualFunc(void) {};
};
template <class DCLASS>
class SomeClass {
public:
// inline here
void test( void ) { Temp.TestVirtualFunc(); };
private:
DCLASS Temp;
};
// 用法
SomeClass <D1> Temp.test();
SomeClass <D2> Temp.test();
========== 如何選擇 ==========
對於到底是使用 Virtual ,還是使用Template或者Virtual的最佳化形式, 在大多數情況下不難做出決定。具體選擇的過程,只需要問問自己到底是程式的速度重要,還是其他重要,也就是,要在:速度,程式結構,靈活性,易用易維護性,代碼的複雜度,代碼大小這些中間做出選擇。
如果速度排第一位,那麼就使用template或者最佳化,如果其他排在速度的前面,應該盡量的使用virtual。
================ pure virtual function ================
pure virtual function主要是用在abstract class裡。有pure virtual function的class,就是abstract class,是不能被執行個體化的。因此,pure virtual function的代碼只有在指定的情況下才能被調用。 例如:
class Test {
virtual void VirtualFunc(void) = 0 { cout<<"Test"<<endl; };
};
class Test2 : public Test {
virtual void VirtualFunc(void) { Test::VirtualFunc(); };
};
================ virtual inheritance ================
其實Virtual Inheritance只有可能在Multiple Inheritance中使用。對於Muliple Inheritance, Aear是堅決反對反對再反對的。Aear個人認為,single inheritance是足夠用的,Java就是最好的例子。 MI實在是太讓人頭疼了,所以Aear不會在這個系列中講MI,這裡只說說 Virtual Inheritance和普通的Inheritance的區別。
class Base {
virtual ~Base();
};
class Derived1 : public Base {
};
class Derived2 : virtual public Base {
};
這裡:
sizeof(Derived1) == 4
sizeof(Derived2) == 8
sizeof(Derived1) == 4 是因為 Derived1繼承 Base以後,使用Derived1的 _vptr
sizeof(Derived1) == 8 是因為由於在所有的vritual inheritance裡,Base作為基類在記憶體中只能出現一次。所以必須把Base和Derived2的附加部分單獨對待。因此有2個_vptr,一個是Base的,一個是Derived2的。
virtual inheritance的直接結果就是大大增加了指令緩衝失效的可能性,同時降低了類內部資料的訪問速度。因為Base 和 Derived2的內部資料不再是放在連續的記憶體中了。如果virtual inheritance的層次越多,對運行速度的影響就越大。
所以Aear在這裡極度不推薦MI和Virtual Inheritance.
================ virtual 的一些用法 ================
下面是virtual的一些用法。
========== virtual destructor ==========
這個比較簡單,就是所有的Base Class,都應該是 public virtual destructor 或者是protected non-virtual destructor,例如:
class Base {
public:
virtual ~Base();
};
或者
class Base {
protected:
~Base();
};
========== virtual constructor ==========
其實本沒有virtual constructor,不過C++中有特殊的實現,實作類別似virtual constructor 和 virtual copy constructor的。代碼如下:
class Base {
virtual Base * construct (void) { return new Base(); };
virtual Base * clone(void) { return new Base(*this); };
};
class Derived : public Base {
virtual Base * construct (void) { return new Derived(); };
virtual Base * clone(void) { return new Derived(*this); };
};
========== pure virtual destructor ==========
如果我們想設定base class 為abstract class,而又只有一個destructor作為唯一的成員函數,可以把它設成為pure virtual
class Base {
public:
virtual ~Base() = 0;
};
========== protected & private virtual ==========
對於virtual function是不是應該放在public裡邊,學術結論和現實中的代碼往往不能統一。雖然很多人認為virtual function 不應該放在public裡邊,但是這樣的設計經常會造成編碼中的不方便,
實際上對與Java來說,protected 或 private 所有 virtual function,幾乎是不太可能的。因此,Aear個人觀點,是不是要protected & private virtual function,要根據情況而定。下面是個private virtual的例子:
class Base {
public:
// this is interface
void print(void) { output() };
private:
virtual void output (void) { cout<<"Base"<<endl; };
};
實際上這樣的處理,要比public virtual速度上慢點。
========== 一些要點 ==========
1. 不建議使用MI (Mission Impossible?), 在大多數情況下,它引入的問題,要比它解決的問題多的多的多。(純個人觀點)
2. 類階層越多,類的效率越低。
3. Virtual Inheritance的類成員訪問效率要低於 Non-virtual Inheritance.
#include <iostream>
using namespace std;
class Base
{
public:
Base() { this->In(); }
virtual ~Base() { this->Out(); }
virtual void In() { cout << "Base::In" << endl;}
virtual void Out() { cout << "Base::Out" << endl;}
};
class Derived : public Base
{
public:
Derived() { this->In();}
virtual ~Derived() { this->Out();}
void In() { cout << "Derived::In" << endl;}
void Out() { cout << "Derived::Out" << endl;}
};
int main()
{
Base* p = new Derived;
p->Out();
delete p;
return 0;
}
輸出結果:
Base::In
Derived::In
Derived::Out
Derived::Out
Base::Out
一目瞭然。不過指標 P 所指的執行個體,就要包含兩張虛函數表,它的結構大概如下:
[
Base 的虛函數表
[func In] address1
[func Out] address2
]
[
Base 的data members
…
]
[
Derived 的虛函數表
[func In] address4
[func Out] address5
]
[
Derived 的data members
…
]
首先說說虛解構函式
建構函式是不可以為虛函數的,這個很明確哈
對於解構函式呢,因為對於多態中,子類可能有一些父類沒有的東東要釋放,所以虛解構函式是有必要的,也就是在繼承中設計虛解構函式是有必要的。
那麼,一個問題:在虛解構函式中調用虛函數(普通虛函數)會發生什麼呢?
從過程來想:在建立一個對象時,會依次調用父建構函式-》子建構函式,同樣,在析構時也會逆過來調用子解構函式-》父解構函式。那麼在調用的時候,如果解構函式中對於虛函數還執行虛機制,就有可能已經執行過一個子物件的解構函式,又去調用他的函數,這樣會很危險。所以在虛解構函式中,對於虛函數,只會執行目前最外一級的那個函數。
同樣,在建構函式中,也不可以用虛機制去調用還沒有建立的子物件的函數,所以都是這樣調用的,調用本地函數,而不用虛機制。
本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/panlong1987/archive/2007/11/09/1875952.aspx