dynamic_cast (expression)

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  [email protected]
///////////////////////////////////////////////////////////////////////////
#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 polymorphism
	mother_d->Speak();
	mother_d->Work();
	//mother_d->Study(); //Error, no such method
	
	cout << "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 polymorphism
	father_s->Speak();	
	father_s->Work();
	//father_s->Study(); //Error, no such method
	
	cout << 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 safe
	if (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 safe
	if (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 cast
	
	cout << "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的結果顯示是空指針,而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。這是通過檢查"運行期類型信息"(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的源類型進行轉換時,編譯也會失敗。

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章