C++基/抽象類的構造/析構(純)虛函數


一、析構函數可定義為純虛函數,但也必須給出函數定義

   Effective C++ 條歀07: 為多態基類聲明virtual析構函數(Declare destructors virtual in polymorphic base classes)

  在某些類里聲明純虛析構函數很方便。純虛函數將產生抽象類——不能實例化的類(即不能創建此類型的對象)。有些時候,你想使一個類成為抽象類,但剛好又沒有任何純虛函數。怎么辦?因為抽象類是准備被用做基類的,基類必須要有一個虛析構函數,純虛函數會產生抽象類,所以方法很簡單:在想要成為抽象類的類里聲明一個純虛析構函數。

1 //這里是一個例子:
2 class awov {
3 public:
4     virtual ~awov() = 0;   // 聲明一個純虛析構函數
5 };

  這個類有一個純虛函數,所以它是抽象的,而且它有一個虛析構函數,所以不會產生析構函數問題。但這里還有一件事:必須提供純虛析構函數的定義:
  awov::~awov() {  ...  }     // 純虛析構函數的定義
  這個定義是必需的,因為虛析構函數工作的方式是:最底層的派生類的析構函數最先被調用,然后各個基類的析構函數被調用。這就是說,即使是抽象類,編譯器也要產生對~awov的調用,所以要保證為它提供函數體。如果不這么做,鏈接器就會檢測出來,最后還是得回去把它添上。

二、構造函數能否定義為虛函數

  關於C++為什么不支持虛擬構造函數,Bjarne很早以前就在C++Style and Technique FAQ里面做過回答

  Avirtual call is a mechanism to get work done given partialinformation. In particular, "virtual" allows us to call afunction knowing only an interfaces and not the exact type of theobject. To create an object you need complete information. Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot bevirtual.

  含義大概是這樣的:虛函數調用是在部分信息下完成工作的機制,允許我們只知道接口而不知道對象的確切類型。 要創建一個對象,你需要知道對象的完整信息。 特別是,你需要知道你想要創建的確切類型。 因此,構造函數不應該被定義為虛函數。

  從C++之父Bjarne的回答我們應該知道C++為什么不支持構造函數是虛函數了,簡單講就是沒有意義。虛函數的作用在於通過子類的指針或引用來調用父類的那個成員函數。而構造函數是在創建對象時自己主動調用的,不可能通過子類的指針或者引用去調用。

  網絡上還有一個很普遍的解釋是這樣的:虛函數相應一個指向vtable虛函數表的指針,但是這個指向vtable的指針事實上是存儲在對象的內存空間的。假設構造函數是虛的,就須要通過 vtable來調用,但是對象還沒有實例化,也就是內存空間還沒有,怎么找vtable呢?所以構造函數不能是虛函數。

  本人對這個觀點並不認同,這主要是因為用什么方式實現虛函數是編譯器的事情,使用Vtable只是大多數編譯器采用的一種手段,並不代表編譯器實現不了虛構造函數,編譯器之所以不支持虛構造函數主要原因就是沒有必要,所以正好這種實現方式也不支持,巧合而已。

三、基礎知識

  純虛函數是一種特殊的虛函數,在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。這就是純虛函數的作用。

  虛函數是C++語言的精髓。含有純虛函數的類被稱為抽象類,不能被實例化生成對象,只能派生。由它派生的類的純虛函數如果沒有被實現,那么,該派生類還是個抽象類。 只有全部實現了純虛函數的派生類才可以被實例化。

定義一個函數為純虛函數,一般表示該函數沒有被實現。但是,這不代表純虛函數不能被實現。純虛函數也是可以定義的。

  虛析構函數是為了讓通過基類指針或引用可以正確釋放派生類對象。有時候如果想讓基類成為一個抽象類,也就是不能被實例化,可以為類引入一個純虛函數。但如果手上沒有任何pure virtual函數時,該怎么辦?由於抽象類總是會被作為基類用於派生的,而基類就該有一個虛的析構函數,並且由純虛函數可以導致抽象類。所以常常把基類的析構函數聲明為純虛析構函數。又由於所有對象析構時,最后都會調用其基類的析構函數,所以基類的析構函數必須有定義。純虛析構函數也不例外。

  虛函數的一些常見問題:

  1. 虛函數是動態綁定的,也就是說,使用虛函數的指針和引用能夠正確找到實際類的對應函數,而不是執行定義類的函數。這是虛函數的基本功能,就不再解釋了。
  2. 構造函數不能是虛函數。而且,在構造函數中調用虛函數,實際執行的是父類的對應函數,因為自己還沒有構造好, 多態是被disable的。
  3. 析構函數可以是虛函數,而且,在一個復雜類結構中,這往往是必須的。
  4. 將一個函數定義為純虛函數,實際上是將這個類定義為抽象類,不能實例化對象。
  5. 純虛函數通常沒有定義體,但也完全可以擁有, 甚至可以顯示調用。
  6. 析構函數可以是純虛的,但純虛析構函數必須有定義體,因為析構函數的調用是在子類中隱含的。
  7. 非純的虛函數必須有定義體,不然是一個錯誤。
  8. 派生類的override虛函數定義必須和父類完全一致(C++11中使用override進行編譯器檢查)。除了一個特例,如果父類中返回值是一個指針或引用,子類override時可以返回這個指針(或引用)的派生。例如,在上面的例子中,在Base中定義了 virtual Base* clone(); 在Derived中可以定義為 virtual Derived* clone()。可以看到,這種放松對於Clone模式是非常有用的(也就是說override並不會檢查返回值類型)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM