C++基本功和 Design Pattern系列 virtual

來源:互聯網
上載者:User

關於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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.