【原創】Performanced C++ 經驗規則 第四條:靜態和多態,亦敵亦友

來源:互聯網
上載者:User

第四條:靜態和多態,亦敵亦友

這一篇,我們討論C++中靜態和多態的關係。我們都知道,C++並不是一門“動態”語言,雖然它提供了同樣強大於其它動態語言的多態性,但很多時候,我們之所以選擇C++,看重中正是其“靜態”所帶來的High Performance。所謂靜態,通常是指,在程式啟動並執行過程,是“靜止”不變,固定的(特別是記憶體位址),當然“多態”就是與之對立的概念。這一篇我們並不討論靜態(成員)變數或靜態(成員)函數有什麼作用,而是討論“靜態”的行為,對比“多態”。我們這裡所說的靜態,是指:compiler time,即編譯時間綁定、早綁定、靜態聯編;而“多態”就是真正的runtime綁定、晚綁定、動態聯編。

很奇妙,這一組對立的概念,卻可以在C++中和平共存,時而協同工作。

老規矩,還是一小段代碼提出問題,當一個虛成員函數(多態性)在其子類中被聲明為靜態成員函數時(或相反過來),會發生什嗎?

1、當虛函數遭遇靜態函數

 1 #include <iostream> 2 using namespace std; 3  4 class Base 5 { 6 public: 7     virtual void foo(void){ cout << "Base::foo()" << endl; } 8 }; 9 10 class Derived : public Base11 {12 public:13     void foo(void){ cout << "Derived::foo()" << endl; }14 } ;15 16 class DerivedAgain : public Derived17 {18 public:19     static void foo(void){ cout << "DerivedAgain::foo()"<< endl; }20 } ;21 22 int main(int argc, char** argv)23 {24     DerivedAgain da;25     Base* pB = &da;26 27 28     da.foo();29     pB->foo(); 30     return 0;31 }

上述代碼運行結果是什嗎?等等,你確定上述代碼能通過編譯?在筆者Ubuntu 12.04 + gcc 4.6.3的機器上,上述代碼編譯不能通過。顯示如下資訊:

1 stawithvir.cpp:19:17: error: ‘static void DerivedAgain::foo()’ cannot be declared2 stawithvir.cpp:13:10: error:   since ‘virtual void Derived::foo()’ declared in base class

很明顯,編譯不能通過的原因,是在DerivedAgain類中將虛函式宣告為static,編譯器拒絕此“靜態”與“多態”的和平共處。此時理由很簡單,static成員函數,是類級共用的,不屬於任何對象,也不會傳入this指標,不能訪問非靜態成員;然而,虛函數的要求與此正相反,需要綁定對象(this指標),進而獲得虛表,然後進行調用。如此矛盾的行為,編譯器情何以堪,因為選擇報錯來表達其不滿。我們可以暫時記住結論:不能將虛函式宣告為靜態

接下來你可能會問,編譯都不能通過的東西,對錯不是明擺著的嗎?為什麼還要拿來討論,這是因為,在某些編譯器上(可以在VC6,VC2008等嘗試),該代碼能編譯通過,並輸出結果,不可思議?不過這些編譯器同時也給出了一個警告(參與MSDN warning c4526),指出靜態函數不能用做虛函數進行調用。雖然通過了編譯,但思想與上述Gcc是一致的。

1 //輸出結果2 DerivedAgain::foo()3 Derived::foo()

da.foo()輸出DerivedAgain::foo()沒有疑問(通過對象調用方法,無論是否虛方法,本來就不會產生動態綁定,即無虛特性);而pB->foo()輸出Derived::foo()則需要解釋一下,因為pB是指標調用虛方法,產生“多態”,動態綁定時發現pB指向的物件類型為DerivedAgain,於是去尋找DerivedAgain對象虛表中foo()的地址,但此時發現DerivedAgain的虛表中foo()的地址其實是Derived::foo(),因為DerivedAgain中的foo已經被聲明為static,不會更新此函數在虛表中的地址(實際上,由於DerivedAgain沒有聲明任何新的虛函數,它對象的虛表同Derived對象是完全一樣的,如果有興趣,可以通過彙編查看),所以輸出的是Derived::foo(),也從一個側面證明了:在繼承鏈中,使用最"新"的虛函數版本。

至此,這個問題已經解釋清楚,再次記住結論:靜態成員函數,不能同時也是虛函數

 

2、重載(overload)並非真正的多態,其本質是靜態行為

筆者曾不止一次的看到,許多書籍、資料,在談到C++多態性的時候,經常把“重載”(overload)歸入多態行為中。這種說法看似也沒什麼不正確,實際上我認為十分不妥。雖然重載,通過區分特徵標的不同(注意,同函數名而參數不同、或同函數名但是否是const成員函數,都是重載依據),而使相同函數名的方法調用產生了不同的行為,確實體現了“多態”的思想,但重載的本質是靜態繫結,是編譯期就能確定調用哪個方法,而非動態綁定,所以不是真正的多態。所以,頭腦要清醒,即如果兩個(或多個)方法之間的關係是“重載”(overload),那麼就不會有真正的多態行為產生。

 

3、何時產生真正的多態?

討論重載之後,就要談到,何時產生真正的多態行為,即動態綁定呢?筆者歸納三個必要條件如下:

(1)方法是虛的;

(2)有覆蓋(override)產生;

(3)通過指標或引用調用相應的虛方法,而非通過對象調用;通過對象調用方法,無論方法是否是虛方法,均是靜態聯編行為。

條件(1)(2)很明顯,如果方法是虛的也沒有覆蓋,何來“多”的“態”?而條件(3)容易被新手忽視,因為通過對象調用,對象的類型已經確知,所以靜態繫結,不會再產生多態。而通過指標或引用調用相應虛方法,由於在編譯期不能確定指標或引用指向的具體類型,所以只能動態聯編,從而產生多態

 

4、不正確的代碼將阻止多態行為

好了,接下來我們看一小段代碼,來自《C++ Primer Plus》:

 1 class Base 2 { 3 public: 4     virtual void foo(void) {...} 5     ... 6 }; 7  8 class Derived : public Base 9 {10 public:11     void foo(void) {...}12     ...13 };14 15 //版本116 void show1(const Base& b)17 {18     b.foo();19 }20 21 //版本222 void show2(Base b)23 {24    b.foo();25 }26 27 int main(int argc, char** argv)28 {29     Derived d;30     show1(d);31     show2(d);32     return 0;33 }

上述代碼有什麼問題?我們看到,兩個版本的show函數唯一不同之處,就是版本1按引用傳遞對象,版本2按值傳遞對象。在main函數中,建立了一個Derived對象並傳給版本1函數,由於版本1中的參數b是參考型別,OK,沒有問題,b.foo()將按照b實際指向的對象調用,即可以正確調用Derived::foo();而版本2參數b是物件類型(b是Base(const Base&)拷貝構造建立的一個Base對象,自動向上的強制類型轉換使得基類拷貝建構函式可以引用一個子類對象),根據上述第3點,則b.foo()將按物件類型(Base)調用到Base::foo(),不產生多態行為。即,由於按值傳遞,在此處阻止了動態綁定,阻止了多態行為

說到這裡的話,又是老生常談的問題,即除非必須要這樣做,否則不要按值方式傳遞參數,而應選擇指標或引用,關於這個問題,本系列後面還會再談。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.