C++標準轉換運算子dynamic_cast

來源:互聯網
上載者:User
dynamic_cast <new_type> (expression)

dynamic_cast運算子,應該算是四個裡面最特殊的一個,因為它涉及到編譯器的屬性設定,而且牽扯到的物件導向的多態性跟程式運行時的狀態也有關係,所以不能完全的使用傳統的轉換方式來替代。但是也因此它是最常用,最不可缺少的一個運算子。

與static_cast一樣,dynamic_cast的轉換也需要目標類型和來源物件有一定的關係:繼承關係。 更準確的說,dynamic_cast是用來檢查兩者是否有繼承關係。因此該運算子實際上只接受基於類對象的指標和引用的類轉換。從這個方面來看,似乎dynamic_cast又和reinterpret_cast是一致的,但實際上,它們還是存在著很大的差別。

還是用代碼來解釋,讓編譯器來說明吧。

/////////////////////////////////////////////////////////////////////////////// cast_operator_comparison.cpp                                                      // Language:   C++                   // Complier:    Visual Studio 2010, Xcode3.2.6 // Platform:    MacBook Pro 2010// Application:  none  // Author:      Ider, Syracuse University  ider.cs@gmail.com///////////////////////////////////////////////////////////////////////////#include <string>#include <iostream>using namespace std;class Parents{public:Parents(string n="Parent"){ name = n;}virtual ~Parents(){}virtual void Speak(){cout << "\tI am " << name << ", I love my children." << endl;}void Work(){cout << "\tI am " << name <<", I need to work for my family." << endl;;}protected:string name;};class Children : public Parents{public:Children(string n="Child"):Parents(n){ }virtual ~Children(){}virtual void Speak(){cout << "\tI am " << name << ", I love my parents." << endl;}/* **Children inherit Work() method from parents, **it could be treated like part-time job. */void Study(){cout << "\tI am " << name << ", I need to study for future." << endl;;}private://string name; //Inherit "name" member from Parents};class Stranger {public:Stranger(string n="stranger"){name = n;}virtual ~Stranger(){}void Self_Introduce(){cout << "\tI am a stranger" << endl;}void Speak(){//cout << "I am a stranger" << endl;cout << "\tDo not talk to "<< name << ", who is a stranger." << endl;}private:string name;};int main() {/******* cast from child class to base class *******/cout << "dynamic_cast from child class to base class:" << endl;Children * daughter_d = new Children("Daughter who pretend to be my mother");Parents * mother_d = dynamic_cast<Parents*> (daughter_d); //right, cast with polymorphismmother_d->Speak();mother_d->Work();//mother_d->Study(); //Error, no such methodcout << "static_cast from child class to base class:" << endl;Children * son_s = new Children("Son who pretend to be my father");Parents * father_s = static_cast<Parents*> (son_s); //right, cast with polymorphismfather_s->Speak();father_s->Work();//father_s->Study(); //Error, no such methodcout << endl;/******* cast from base class to child class *******/cout << "dynamic_cast from base class to child class:" << endl;Parents * father_d = new Parents("Father who pretend to be a my son");Children * son_d = dynamic_cast<Children*> (father_d); //no error, but not safeif (son_d){son_d->Speak();son_d->Study();}else cout << "\t[null]" << endl;cout << "static_cast from base class to child class:" << endl;Parents * mother_s = new Parents("Mother who pretend to be a my daugher");Children * daughter_s = static_cast<Children*> (mother_s);  //no error, but not safeif (daughter_s){daughter_s->Speak();daughter_s->Study();}else cout << "\t[null]" << endl;cout << endl;/******* cast between non-related class *******/cout << "dynamic_cast to non-related class:" << endl;Stranger* stranger_d = dynamic_cast<Stranger*> (daughter_d);if (stranger_d){stranger_d->Self_Introduce();stranger_d->Speak();}else cout <<"\t[null]"<<endl;//Stranger* stranger_s = static_cast<Stranger*> (son_s);    //Error, invalid castcout << "reinterpret_cast to non-related class:" << endl;Stranger* stranger_r = reinterpret_cast<Stranger*> (son_s);if (stranger_r){stranger_d->Self_Introduce();//stranger_d->Speak();//This line would cause program crush,//as "name" could not be found corretly.}else cout << "\t[null]" << endl;cout << endl;/******* cast back*******/cout << "use dynamic_cast to cast back from static_cast:" << endl;Children* child_s = dynamic_cast<Children*> (father_s);if (child_s){child_s->Speak();child_s->Work();}else cout << "\t[null]" << endl;    //cout<<typeid(stranger_r).name()<<endl;    cout << "use dynamic_cast to cast back from reinterpret_cast:" << endl;Children* child_r = dynamic_cast<Children*> (stranger_r);if (child_r){child_r->Speak();child_r->Work();}else cout << "\t[null]" << endl;delete daughter_d;delete son_s;delete father_d;delete mother_s;return 0;}/********************* Result *********************///dynamic_cast from child class to base class://I am Daughter who pretend to be my mother, I love my parents.//I am Daughter who pretend to be my mother, I need to work for my family.//static_cast from child class to base class://I am Son who pretend to be my father, I love my parents.//I am Son who pretend to be my father, I need to work for my family.////dynamic_cast from base class to child class://[null]//static_cast from base class to child class://I am Mother who pretend to be a my daugher, I love my children.//I am Mother who pretend to be a my daugher, I need to study for future.////dynamic_cast to non-related class://[null]//reinterpret_cast to non-related class://I am a stranger////use dynamic_cast to cast back from static_cast://I am Son who pretend to be my father, I love my parents.//I am Son who pretend to be my father, I need to work for my family.//use dynamic_cast to cast back from reinterpret_cast://[null]  

從上邊的代碼和輸出結果可以看出:

對於從子類到基類的指標轉換,static_cast和dynamic_cast都是成功並且正確的(所謂成功是說轉換沒有編譯錯誤或者運行異常;所謂正確是指方法的調用和資料的訪問輸出是期望的結果),這是物件導向多態性的完美體現。

而從基類到子類的轉換,static_cast和dynamic_cast都是成功的,但是正確性方面,我對兩者的結果都先進行了是否非空的判別:dynamic_cast的結果顯示是null 指標,而static_cast則是非null 指標。但很顯然,static_cast的結果應該算是錯誤的,子類指標實際所指的是基類的對象,而基類對象並不具有子類的Study()方法(除非媽媽又想去接受個"繼續教育")。

對於沒有關係的兩個類之間的轉換,輸出結果表明,dynamic_cast依然是返回一個null 指標以表示轉換是不成立的;static_cast直接在編譯期就拒絕了這種轉換。

reinterpret_cast成功進行了轉換,而且返回的值並不是null 指標,但是結果顯然是錯誤的,因為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判定該類型已經不是原來的類型結果,轉換得到了一個null 指標。

總得說來,static_cast和reinterpret_cast運算子要麼直接被編譯器拒絕進行轉換,要麼就一定會得到相應的目標類型的值。 而dynamic_cast卻會進行判別,確定源指標所指的內容,是否真的合適被目標指標接受。如果是否定的,那麼dynamic_cast則會返回null。這是通過檢查"運行期類型資訊"(Runtime type information,RTTI)來判定的,它還受到編譯器的影響,有些編譯器需要設定開啟才能讓程式正確運行(導師的PPT詳細介紹了Visual Studio的情況),因此dynamic_cast也就不能用傳統的轉換方式來實現了。

虛函數(virtual function)對dynamic_cast的作用

已經在前面反覆提到過物件導向的多態性,但是這個多態性到底要如何體現呢?dynamic_cast真的允許任意對象指標之間進行轉換,只是最後返回個null值來告知轉換無結果嗎?

實際上,這一切都是虛函數(virtual function)在起作用。

在C++的面對對象思想中,虛函數起到了很關鍵的作用,當一個類中擁有至少一個虛函數,那麼編譯器就會構建出一個虛函數表(virtual method table)來指示這些函數的地址,假如繼承該類的子類定義並實現了一個同名並具有同樣函數簽名(function siguature)的方法重寫了基類中的方法,那麼虛函數表會將該函數指向新的地址。此時多態性就體現出來了:當我們將基類的指標或引用指向子類的對象的時候,調用方法時,就會順著虛函數表找到對應子類的方法而非基類的方法。

當然虛函數表的存在對於效率上會有一定的影響,首先構建虛函數表需要時間,根據虛函數表尋到到函數也需要時間。

因為這個原因如果沒有繼承的需要,一般不必在類中定義虛函數。但是對於繼承來說,虛函數就變得很重要了,這不僅僅是實現多態性的一個重要標誌,同時也是dynamic_cast轉換能夠進行的前提條件。

假如去掉上個例子中Stranger類解構函式前的virtual,那麼語句

Children* child_r = dynamic_cast<Children*> (stranger_r);

在編譯期就會直接報出錯誤,具體原因不是很清楚,我猜測可能是因為當類沒有虛函數表的時候,dynamic_cast就不能用RTTI來確定類的具體類型,於是就直接不通過編譯。

這不僅僅是沒有繼承關係的類之間的情況,如果基類或者子類沒有任何虛函數(如果基類有虛函數表,子類當然是自動繼承了該表),當他們作為dynamic_cast的源類型進行轉換時,編譯也會失敗。

這種情況是有可能存在的,因為在設計的時候,我們可能不需要讓子類重寫任何基類的方法。但實際上,這是不合理的。導師在講解多態性的時候,時刻強調了一點:如果要用繼承,那麼一定要讓解構函式是虛函數;如果一個函數是虛函數,那麼在子類中也要是虛函數。

我會將導師關於"為何繼承中解構函式必須是虛函數"的講解總結一下,當然你也可以看這邊文章來瞭解原因。

Director: Jim Fawcett
  1. C++ Language Tutorial - Type Casting
  2. Object Oriented Design
  3. IBM Complilers - XL C/C++ V9.0 for Linux - The dynamic_cast operator (C++ only)
  4. MSDN Visual C++ Develope Center - dynamic_cast Operator
  5. In C++, what’s a virtual destructor and when is it needed?
  6. Wikipedia The Free Encyclopedia - Run-Time Type Information
  7. Wikipedia The Free Encyclopedia - Virtual Function
相關文章

聯繫我們

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