標籤:

構造函數不能是虛函數?

針對 「構造函數不能是虛函數」 這句話,如果我的類有別的構造函數,比如顯式定義了默認構造函數,而同時把帶一個參數的構造函數聲明為虛函數,這樣有何不可?因為這個類的全部對象我完全可以通過那個顯式聲明的默認構造函數來構造對象,因此指向vtable的指針也能被初始化,之後不就可以調用那個虛構造函數了嗎?


Delphi的構造函數可以是虛函數,所以父類構造函數裡面還能調用到子類的虛函數的重寫版本,這對於寫GUI是多麼牛逼的一個feature啊。C++不行,所以MFC才被迫搞什麼兩階段構造。結果這個垃圾設計後來竟然被Google寫進了他們的coding convention裡面。


其實在虛繼承的情況下,虛基類的初始化部分是虛的。


我上一次回答這個問題,大概是2002年在http://delphibbs.com,這應該是當年關於C++/Delphi比較的最熱門的問題之一。

首先,C++不支持虛擬構造函數,而Delphi支持。

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

A
virtual call is a mechanism to get work done given partial
information. In particular, "virtual" allows us to call a
function knowing only an interfaces and not the exact type of the
object. To create an object you need complete information. In
particular, you need to know the exact type of what you want to
create. Consequently, a "call to a constructor" cannot be
virtual.

出處:Stroustrup: C++ Style and Technique FAQ

大意是說,虛擬函數調用只需要「部分的」信息,即只需要知道函數介面,而不需要對象的具體類型。但是構建一個對象,卻必須知道具體的類型信息。如果你調用一個虛擬構造函數,編譯器怎麼知道你想構建是繼承樹上的哪種類型呢?所以這在邏輯上是一個悖論。

Bjarne建議的解決方案是factory
pattern,也就是為每一個要構建的類型再創建一個對應的factory,把問題放到factory的make方法中去解決。這也是C++中的通用解決方案。

BTW,這個FAQ是在不停地維護和更新中的,但是至少在2002年我翻譯這個FAQ之前,這個回答就存在了。

那麼Delphi為什麼能夠支持虛擬構造函數呢?Delphi有一種類類型,儲存著某個類的meta
data。TClass是所有自定義類類型(不是自定義類型)的祖先類。構建一個新對象的時候,事實上是調用TClass的某個子類(即自定義類類型)的虛擬Create方法,而這個子類儲存了相關類的meta
data,因此避免了「虛擬構造函數不知道具體類型信息」的問題。這可以視為factory
pattern的一種通用實現,在語言級別非常優雅和完美地解決了這個問題。

從這個問題又可以延伸出兩個問題,我在知乎都已經討論過:

C++為什麼不支持類類型,或者說,C++為什麼不支持更豐富的RTTI特性?

C/C++有沒有類似能實現反射、內省和代理之類的技術? - 左輕侯的回答

虛擬構造函數有什麼意義(尤其是在可視化組件技術中)?

VCL比MFC好在哪裡? - 左輕侯的回答

當一個程序員足夠老,他就從工程師變成了歷史學家。


虛函數需要依賴對象中指向類的虛函數表的指針,而這個指針是在構造函數中初始化的(這個工作是編譯器做的,對程序員來說是透明的),如果構造函數是虛函數的話,那麼在調用構造函數的時候,而此時虛函數表指針並未初始化完成,這就會引起錯誤。

根據題主的描述,是在使用默認構造後,再調用有參數的構造函數。這個我是在無法想起如何完成,因為構造函數一般都是由編譯器來調用的,無法在一個已有的對象再調用構造函數。

水平有限,如有錯誤,請指正,謝謝。


在調用構造函數時,並不知道構造的是基類還是派生類,所以沒辦法判斷調用哪個版本的構造函數。所以,這是個悖論。


因為設計C++的人不想


我的理解是構造函數本身涉及到vptr的設定,那麼vptr指向虛表裡面存儲虛函數地址,那麼構造函數是虛函數和構造函數要設定vptr是矛盾的。我的理解,不知道對不對。


構造函數是虛函數有意義嗎。。?new的時候就會按類型創建對象了,基類類型創建基類對象,子類類型創建子類對象,之後才有基類指針指向子類對象需要調用虛函數的情況,除非你對基類指針指向的子類對象顯式調用一遍構造函數,但這完全可以用類似reinit之類的函數實現。


推薦閱讀:

C++ 對象是如何完成成員函數的調用的?
vs2013 有必要 使用 visual assist或resharper嗎?
如何閱讀protobuf源碼?

TAG:C |