dynamic_cast運算符,應該算是四個裡面最特殊的一個,因為它涉及到編譯器的屬性設定,而且牽扯到的面向對象的多态性跟程式運作時的狀态也有關系,是以不能完全的使用傳統的轉換方式來替代。但是也是以它是最常用,最不可缺少的一個運算符。
與一樣,dynamic_cast的轉換也需要目标類型和源對象有一定的關系:繼承關系。
更準确的說,dynamic_cast是用來檢查兩者是否有繼承關系。是以該運算符實際上隻接受基于類對象的指針和引用的類轉換。從這個方面來看,似乎dynamic_cast又和是一緻的,但實際上,它們還是存在着很大的差别。
還是用代碼來解釋,讓編譯器來說明吧。
從上邊的代碼和輸出結果可以看出:
對于從子類到基類的指針轉換,static_cast和dynamic_cast都是成功并且正确的(所謂成功是說轉換沒有編譯錯誤或者運作異常;所謂正确是指方法的調用和資料的通路輸出是期望的結果),這是面向對象多态性的完美展現。
而從基類到子類的轉換,static_cast和dynamic_cast
都是成功的,但是正确性方面,我對兩者的結果都先進行了是否非空的判别:dynamic_cast的結果顯示是空指針,而static_cast則是非空
指針。但很顯然,static_cast的結果應該算是錯誤的,子類指針實際所指的是基類的對象,而基類對象并不具有子類的Study()方法(除非媽媽
又想去接受個"繼續教育")。
對于沒有關系的兩個類之間的轉換,輸出結果表明,dynamic_cast依然是傳回一個空指針以表示轉換是不成立的;static_cast直接在編譯期就拒絕了這種轉換。
reinterpret_cast成功進行了轉換,而且傳回的值并不是空指針,但是結果顯然是錯誤的,因為Children類顯然不具有
Stranger的Self_Introduce()。雖然兩者都具有name資料成員和Speak()方法,,Speak()方法也隻是調用了該相同名
稱的成員而已,但是對于Speak()的調用直接造成了程式的崩潰。
其實前面static_cast的轉換的結果也會跟reinterpret_cast一樣造成的程式的崩潰,隻是類的方法都隻有一份,隻有資料成員屬于對象,
是以在調用那些不會通路對象的資料的方法時(如Stranger的Self_Introduce())并不會造成崩潰。而
daughter_s->Speak();和daughter_s->Study();調用了資料成員卻沒有出現運作錯誤,則是因為該成員是
從基類繼承下來的,通過位址偏移可以正确的到達資料成員所在的位址以讀取出資料。
最後,程式裡還用dynamic_cast希望把用其他轉換運算符轉換過去的指針轉換回來。
對于使用static_cast轉換後指向了子類對象的基類指針,dynamic_cast判定轉換是合理有效的,是以轉換成功獲得一個非空的指針并且正
确輸出了結果;而對于reinterpret_cast轉換的類型,的确如它的功能一樣——重新解析,變成新的類型,是以才得到dynamic_cast
判定該類型已經不是原來的類型結果,轉換得到了一個空指針。
總得說來,static_cast和reinterpret_cast運算符要麼直接被
編譯器拒絕進行轉換,要麼就一定會得到相應的目标類型的值。
而dynamic_cast卻會進行判别,确定源指針所指的内容,是否真的合适被目标指針接受。如果是否定的,那麼dynamic_cast則會傳回
null。這是通過檢查來判定的,它還受到編譯器的影響,有些編譯器需要設定開啟才能讓程式正确運作(導師的詳細介紹了Visual
Studio的情況),是以dynamic_cast也就不能用傳統的轉換方式來實作了。
已經在前面反複提到過,但是這個多态性到底要如何展現呢?dynamic_cast真的允許任意對象指針之間進行轉換,隻是最後傳回個null值來告知轉換無結果嗎?
實際上,這一切都是在起作用。
在C++的面對對象思想中,虛函數起到了很關鍵的作用,當一個類中擁有至少一個虛函數,那麼編譯器就會建構出一個來訓示這些函數的位址,假如繼承該類的子類定義并實作了一個同名并具有同樣的方法重寫了基類中的方法,那麼虛函數表會将該函數指向新的位址。此時多态性就展現出來了:當我們将基類的指針或引用指向子類的對象的時候,調用方法時,就會順着虛函數表找到對應子類的方法而非基類的方法。
當然虛函數表的存在對于效率上會有一定的影響,首先建構虛函數表需要時間,根據虛函數表尋到到函數也需要時間。
因為這個原因如果沒有繼承的需要,一般不必在類中定義虛函數。但是對于繼承來說,虛函數就變得很重要了,這不僅僅是實作多态性的一個重要标志,同時也是dynamic_cast轉換能夠進行的前提條件。
假如去掉上個例子中Stranger類析構函數前的virtual,那麼語句
<code>Children* child_r = dynamic_cast<Children*> (stranger_r);</code>
在編譯期就會直接報出錯誤,具體原因不是很清楚,我猜測可能是因為當類沒有虛函數表的時候,dynamic_cast就不能用RTTI來确定類的具體類型,于是就直接不通過編譯。
這不僅僅是沒有繼承關系的類之間的情況,如果基類或者子類沒有任何虛函數(如果基類有虛函數表,子類當然是自動繼承了該表),當他們作為dynamic_cast的源類型進行轉換時,編譯也會失敗。
這種情況是有可能存在的,因為在設計的時候,我們可能不需要讓子類重寫任何基類的方法。但實際上,這是不合理的。導師在講解多态性的時候,時刻強調了一點:如果要用繼承,那麼一定要讓析構函數是虛函數;如果一個函數是虛函數,那麼在子類中也要是虛函數。
我會将導師關于"為何繼承中析構函數必須是虛函數"的講解總結一下,當然你也可以看來了解原因。
Director: