c++ 中的幾種cast

reinterpret_cast <new_type> (expression)

reinterpret_cast運算符是用來處理無關類型之間的轉換;它會產生一個新的值,這個值會有與原始參數(expressoin)有完全相同的比特位。

什麼是無關類型?我沒有弄清楚,沒有找到好的文檔來說明類型之間到底都有些什麼關係(除了類的繼承以外)。後半句倒是看出了reinterpret_cast的字面意思:重新解釋(類型的比特位)。我們真的可以隨意將一個類型值的比特位交給另一個類型作爲它的值嗎?其實不然。

IBM的C++指南裏倒是明確告訴了我們reinterpret_cast可以,或者說應該在什麼地方用來作爲轉換運算符:

  • 從指針類型到一個足夠大的整數類型
  • 從整數類型或者枚舉類型到指針類型
  • 從一個指向函數的指針到另一個不同類型的指向函數的指針
  • 從一個指向對象的指針到另一個不同類型的指向對象的指針
  • 從一個指向類函數成員的指針到另一個指向不同類型的函數成員的指針
  • 從一個指向類數據成員的指針到另一個指向不同類型的數據成員的指針

不過我在Xcode中測試了一下,事實上reinterpret_cast的使用並不侷限在上邊所說的幾項的,任何類型的指針之間都可以互相轉換,都不會得到編譯錯誤。上述列出的幾項,可能 是Linux下reinterpret_cast使用的限制,也可能是IBM推薦我們使用reinterpret_cast的方式

所以總結來說:reinterpret_cast用在任意指針(或引用)類型之間的轉換;以及指針與足夠大的整數類型之間的轉換;從整數類型(包括枚舉類型)到指針類型,無視大小。

(所謂"足夠大的整數類型",取決於操作系統的參數,如果是32位的操作系統,就需要整形(int)以上的;如果是64位的操作系統,則至少需要長整形(long)。具體大小可以通過sizeof運算符來查看)。

reinterpret_cast有何作用

從上邊對reinterpret_cast介紹,可以感覺出reinterpret_cast是個很強大的運算符,因爲它可以無視種族隔離,隨便搞但就像生物的準則,不符合自然規律的隨意雜交只會得到不能長久生存的物種。隨意在不同類型之間使用reinterpret_cast,也之後造成程序的破壞和不能使用

比如下邊的代碼
typedef int (*FunctionPointer)(int);
int value = 21;
FunctionPointer funcP;
funcP = reinterpret_cast<FunctionPointer> (&value);
funcP(value);

我先用typedef定義了一個指向函數的指針類型,所指向的函數接受一個int類型作爲參數。然後我用reinterpret_cast將一個整型的地址轉換成該函數類型並賦值給了相應的變量。最後,我還用該整形變量作爲參數交給了指向函數的指針變量。

這個過程編譯器都成功的編譯通過,不過一旦運行我們就會得到"EXC_BAD_ACCESS"的運行錯誤,因爲我們通過funcP所指的地址找到的並不是函數入口。

由此可知,reinterpret_cast雖然看似強大,作用卻沒有那麼廣。IBM的C++指南、C++之父Bjarne Stroustrup的FAQ網頁MSDN的Visual C++也都指出:錯誤的使用reinterpret_cast很容易導致程序的不安全,只有將轉換後的類型值轉換回到其原始類型,這樣纔是正確使用reinterpret_cast方式。

這樣說起來,reinterpret_cast轉換成其它類型的目的只是臨時的隱藏自己的什麼(做個臥底?),要真想使用那個值,還是需要讓其露出真面目才行。那到底它在C++中有其何存在的價值呢?

MSDN的Visual C++ Developer Center 給出了它的使用價值:用來輔助哈希函數。下邊是MSNDN上的例子:

                // expre_reinterpret_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
	unsigned int val = reinterpret_cast<unsigned int>( p );
	return ( unsigned short )( val ^ (val >> 16));
}

using namespace std;
int main() {
	int a[20];
	for ( int i = 0; i < 20; i++ )
		cout << Hash( a + i ) << endl;
}

//如果跟我一樣是64位的系統,可能需要將unsigned int改成 unsigned long才能運行。

            

這段代碼適合體現哈希的思想,暫時不做深究,但至少看Hash函數裏面的操作,也能體會到,對整數的操作顯然要對地址操作更方便。在集合中存放整形數值,也要比存放地址更具有擴展性(當然如果存void *擴展性也是一樣很高的),唯一損失的可能就是存取的時候整形和地址的轉換(這完全可以忽略不計)。

不過可讀性可能就不高,所以在這種情況下使用的時候,就可以用typedef來定義個指針類型:
typedef unsigned int PointerType;

這樣不是更棒,當我們在64位機器上運行的時候,只要改成:
typedef unsigned long PointerType;

當reinterpret_cast面對const

IBM的C++指南指出:reinterpret_cast不能像const_cast那樣去除const修飾符。這是什麼意思呢?代碼還是最直觀的表述:


int main() 
{
	typedef void (*FunctionPointer)(int);
	int value = 21;
	const int* pointer = &value;
	
	//int * pointer_r = reinterpret_cast<int*> (pointer); 
	// Error: reinterpret_cast from type 'const int*' to type 'int*' casts away constness
	
	FunctionPointer funcP = reinterpret_cast<FunctionPointer> (pointer);
}

例子裏,我們像前面const_cast一篇舉到的例子那樣,希望將指向const的指針用運算符轉換成非指向const的指針。但是當實用reinterpret_cast的時候,編譯器直接報錯組織了該過程。這就體現出了const_cast的獨特之處。

但是,例子中還有一個轉換是將指向const int的指針付給指向函數的指針,編譯順利通過編譯,當然結果也會跟前面的例子一樣是無意義的。

如果我們換一種角度來看,這似乎也是合理的。因爲
const int* p = &value;
int * const q = &value;

這兩個語句的含義是不同的,前者是"所指內容不可變",後者則是"指向的地址不可變"(具體參考此處)。因此指向函數的指針默認應該就帶有"所指內容不可變"的特性。

畢竟函數在編譯之後,其操作過程就固定在那裏了,我們唯一能做的就是傳遞一些參數給指針,而無法改變已編譯函數的過程。所以從這個角度來想,上邊例子使用reinterpret_cast從const int * 到FunctionPointer轉換就變得合理了,因爲它並沒有去除const限定



static_cast <new_type> (expression)

雖然const_cast是用來去除變量的const限定,但是static_cast卻不是用來去除變量的static引用。其實這是很容易理解的,static決定的是一個變量的作用域和生命週期,比如:在一個文件中將變量定義爲static,則說明這個變量只能在本Package中使用;在方法中定義一個static變量,該變量在程序開始存在直到程序結束;類中定義一個static成員,該成員隨類的第一個對象出現時出現,並且可以被該類的所有對象所使用。

對static限定的改變必然會造成範圍性的影響,而const限定的只是變量或對象自身。但無論是哪一個限定,它們都是在變量一出生(完成編譯的時候)就決定了變量的特性,所以實際上都是不容許改變的。這點在const_cast那部分就已經有體現出來。

static_cast和reinterpret_cast一樣,在面對const的時候都無能爲力:兩者都不能去除const限定。兩者也存在的很多的不同,比如static_cast不僅可以用在指針和引用上,還可以用在基礎數據和對象上;前面提到過reinterpret_cast可以用在"沒有關係"的類型之間,而用static_cast來處理的轉換就需要兩者具有"一定的關係"了。

還是用例子來說明比較直觀一些。

還是用例子來說明比較直觀一些。

reinterpret_cast一篇,已經提到過reinterpret_cast可以在任意指針之間進行互相轉換,即使這些指針所指的內容是毫無關係的,也就是說一下語句,編譯器是不會報錯的,但是對於程序來說也是毫無意義可言的,只會造成程序崩潰:


#include <iostream>
using namespace std;
unsigned short Hash( void *p ) {
	unsigned long val = reinterpret_cast<unsigned long>( p );
	return ( unsigned short )( val ^ (val >> 16));
}

class Something
{
	/* Some codes here */
};

class Otherthing
{
	/* Some codes here */
};

int main() {
	
	typedef unsigned short (*FuncPointer)( void *) ;
	FuncPointer fp = Hash;	//right, this is what we want

	int a[10];
	const int* ch = a; //right, array is just like pointer
	char chArray[4] = {'a','b','c','d'};
	fp = reinterpret_cast<FuncPointer> (ch); //no error, but does not make sense
	ch = reinterpret_cast<int*> (chArray);	//no error
	
	cout <<hex<< *ch;	//output: 64636261	//it really reinterpret the pointer

	
	Something * st = new Something();
	Otherthing * ot = reinterpret_cast<Otherthing*> (st); //cast between objects with on relationship
}

而以上轉換,都是static_cast所不能完成的任務,也就是說把上邊程序裏所有的reinterpret_cast換成static_cast的話,就會立即得到編譯錯誤,因爲目標指針和原始指針之間不存在"關係"

從上邊的程序,也就一下子看出來了reinterpret_cast和static_cast之間最本質的區別。

而以上轉換,都是static_cast所不能完成的任務,也就是說把上邊程序裏所有的reinterpret_cast換成static_cast的話,就會立即得到編譯錯誤,因爲目標指針和原始指針之間不存在"關係"

從上邊的程序,也就一下子看出來了reinterpret_cast和static_cast之間最本質的區別。

對於static_cast所需要的關係,"繼承"絕對是其中之一,所以static_cast支持指向基類的指針和指向子類的指針之間的互相轉換:


class Parents
{
public:
	virtual ~Parents(){}
	/*codes here*/
};

class Children : public Parents
{
	/*codes here*/
};

int main() 
{	
	Children * daughter = new Children();
	Parents * mother = static_cast<Parents*> (daughter); //right, cast with polymorphism
	
	Parents * father = new Parents();
	Children * son = static_cast<Children*> (father); //no error, but not safe
}

但是從基類到子類的轉換,用static_cast並不是安全的,具體的問題會在dynamic_cast一篇闡述。

在指針和引用方便,似乎也只有繼承關係是可以被static_cast接受的,其他情況的指針和引用轉換都會被static_cast直接扔出編譯錯誤,而這層關係上的轉換又幾乎都可以被dynamic_cast所代替。這樣看起來static_cast運算符的作用就太小了。

實際上static_cast真正用處並不在指針和引用上,而在基礎類型和對象的轉換上 。 而基於基礎類型和對象的轉換都是其他三個轉換運算符所辦不到的。

這些轉換跟C++用戶自定義類型轉換一文中所設計的內容比較接近,所以在那邊文章中出現轉換可以全部加上static_cast。

基礎類型轉換:
float floatValue = 21.7;
int intValue = 7;

cout << floatValue / 7 << "\t\t" << static_cast<int> (floatValue)/7 <<endl;
cout << intValue/3 << "\t\t" << static_cast<double> (intValue)/3 << endl;

//Output:
//3.1     3
//2       2.33333

從輸出結果可以看出轉換是成功並且正確的。

對於對象的轉換,也是需要又關係的,這層關係就是C++用戶自定義類型轉換中提到的方法:

  • 構造函數(Constructor)
  • 類型轉換運算符(Type –Cast Operator

static_cast會根據上述順序尋找到合適的方法進行類型轉換。

賦值運算符並不被算在內,因爲它自身已經是一種運算符,不能再當做轉換運算符來用。


int main(void) 
{
	Ape a;
 	Human h = static_cast<Human> (a); // using promtion constructor

	Programmer p;
	p = static_cast<Programmer> (h); // using  Programmer-cast operaotor

	//Ape a2;
	//a2 = static_cast<Ape> (p); //Error, assignment operator should be used directly

	return 0;
}

(類的代碼見C++用戶自定義類型轉換,或者下載代碼查看)

傳統轉換方式實現static_cast運算符

從上邊對static_cast分析可以跟看,static_cast跟傳統轉換方式幾乎是一致的,所以只要將static_cast和圓括號去掉,再將尖括號改成圓括號就變成了傳統的顯示轉換方式。在C++用戶自定義類型轉換一文已有很多的介紹了。




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的源類型進行轉換時,編譯也會失敗。

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

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




索引目錄

再談爲何會有那四個轉換運算符

看起來,我應該把導師講過、遺漏的有關C++類型轉換方面的內容都總結成文了,主要內容都在以上幾篇文章中闡述完畢。

上邊的每一篇文章,雖然都單獨着重強調一種轉換方式或運算符,但是也有提到跟其他運算符之間的差異性,以及使用建議,因此基本可以看出各個運算符的使用方式和作用。

在文章也看到const_cast, reinterpret_cast, static_cast都可以用傳統的轉換方式基於指針進行替代。如果結合typeid運算符,那麼dynamic_cast其實也可以用傳統轉換方式實現。

因此不免會有疑惑:這四個轉換運算符不是很多餘。

的確,我剛接觸這些轉換運算符的時候,我也又這樣的疑慮。因爲在跟着導師學習C++,做一些課程項目的過程中,我都不曾使用過這些轉換運算符。一來我需要用到類型轉換的地方很少,二來也還不熟悉這些運算符,所以就儘量避免使用。

不過在花了這麼多時間和精力研究和總結這些轉換運算符之後,我以後一定會更多的去使用他們,因爲傳統的轉換方式能夠實現標準轉換運算符的功能,主要還是基於"C++中的指針可以無條件互相轉換"。因此,對於轉換符的實現,其格式基本都是一致的:用強制轉換的方式,直接轉換指針類型

正因如此,看到這些轉換代碼,有時候並不能馬上理解其目的用意。而如果用轉換運算符來操作,就可以一目瞭然地通過轉換符的名稱知道是在去除const,還是想進行指針的重新定義。

另一個角度來說,編譯器也對轉換運算符做來限制、優化和異常處理,使用他們可以更好地減少錯誤的產生,以及避免傳統轉換沒有達到預期的目的。

所以,如果碰到需要類型轉換的地方,就儘量思考,是否可以用轉換運算符來替代,用哪個是最合適的。下邊就來講講什麼時候用什麼樣的轉換符最合適。

轉換運算符的應用之所

結合網絡上各個站點看到的關於C++轉換符的知識,以及前面那些文章得到的反饋,可以將各個轉換運算符的使用總結如下:

對於傳統的轉換方式(C式或函數式),只在數值類型(包括整型、浮點型、字符類型和枚舉)上使用。這也是延續C的形式,當然這類轉換也是可以用static_cast來替換,但是因爲是基本類型,所以傳統轉換已經很直觀。

對於const_cast轉換運算符,用在需要去除掉const限定的時候。其實這種情況出現的很少,可能的方法在const_cast一文中已經又舉例,不過還是反覆強調, 使用const_cast轉換後,絕對不可試圖修改結果的值。

對於reinterpret_cast轉換運算符,一般用在將對象指針類型轉換到整數類型或者void * (空指針)。如同在文中舉出的隱患,因此注意的是,若要使用其結果,一定要將類型轉換回去後使用。也不要將隨意的整數轉換成指針類型。

對於static_cast轉換運算符,將其用在對象的轉換之上(雖然static_cast也可以用在有繼承關係的類型指針之間,但是還是將這方面的轉換交給dynamic_cast來操作吧), static_cast會調用相應的構造函數或者重載的轉換運算符。

通過文章的留言反饋,以及Google C++ Style Guide的推薦格式,知道對於單參構造函數的存在可能會引發一些隱式的轉換,因此用static_cast也可以明確的指出類型的轉換過程,避免生成多餘的臨時對象造成效率下降。

對於dynamic_cast轉換運算符,將其用在具有繼承關係的指針類型之間的轉換。無論是從基類到子類的轉換,還是子類到基類的轉換,都將dynamic_cast套上去,也算是標識它們是一家子。

如果任何一種基於指針或引用的轉換,套上四個轉換運算符之後都失敗,那麼所要進行的轉換可能就觸到了"雷區"了:進行了沒意義的轉換。比如,對於沒有關係的兩個類型的指針進行了轉換,比如試圖轉換指向方法的指針了。所以轉換運算符對於避免代碼出錯也很有幫助。

基於引用(Reference)的轉換運算符使用

前面的文章中,所以對於轉換運算符的講述和舉例,都是基於指針的。但實際上,這些轉換運算符也可以基於引用來展開。準確說實際上引用類型應該是作爲轉換的目標類型,源類型則是對象變量(當然也可能用的是引用變量,或是取出指針所指的內容,它們用到的都是實際的類對象)。

由於引用類型“定義時必須初始化”的特別,使得它不同於指針類型隨時隨地都調用轉換運算符,基於引用的轉換隻在對引用進行初始化的時候纔會出現。

下邊是const_cast和reinterpret_cast基於引用的運用:


    const int int_constant = 21;
    int& int_ref = const_cast<int&>(int_constant);
    cout << int_ref << endl;
    
    int int_value = 7;
    //long& long_ref = int_value; //Error, can not using reference cross types
    float& long_ref = reinterpret_cast<float&> (int_value);
    cout << long_ref << endl;  
    

對於dynamic_cast的應用基本也是一致的,只是還是限制在具有繼承關係的類型之間。不同於基於指針在轉換時返回null,dynami_cast在基於引用轉換失敗時,會拋出std::bad_cast異常,因爲不能將空值賦給引用類型。如果要抓住這個異常,則需要引入如下頭文件:
#include <typeinfo>

而static_cast轉換符前面已經說過推薦直接用在對象之上,不用在指針上,所以也不太會有需要用在引用類型上的情況出現。

山寨C#的TryParse

C#中有很多簡潔實用的轉換方法,比如從字符串到數值類型的ParseTryParse,還有包含了各種從object對象到數值類型、時間類型的方法的Convert類,以及檢查繼承關係的as運算符

從返回的結果看,C++和dynamic_cast和C#的as很相似,兩者都是在失敗時候返回null。

不過面向對象的關鍵點在於什麼都是以對象爲操作單位,如前所講dynamic_cast看起來更像是一個全局方法。因此我便模仿C#的數值類的TryParse方法,寫了一個包裹dynamic_cast的類型轉換方法:


/////////////////////////////////////////////////////////////////////////////
// dynamic_cast_tryparse.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;;
    }
    
    /************** TryParseTo **************/
    template<typename T> bool TryParseTo(T** outValue)
    {
        T* temp = dynamic_cast<T*> (this);
        if (temp == NULL) return false;
        
        *outValue = temp;
        return true;
    }
    
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() 
{

    Children * parsedChild;
    Parents * parsedParent;
    Stranger * parsedStranger;
    
    Parents * mother = new Parents("Mother who pretend to be a my daugher");
    if(mother->TryParseTo<Children>(&parsedChild))
        parsedChild->Speak();
    else
        cout << "Parents parse to Children failed" << endl;
    
    delete mother;
    
    mother = new Children("Daughter who pretend to be a my mother");
    if(mother->TryParseTo<Children>(&parsedChild))
        parsedChild->Speak();
    else
        cout << "Parents parse to Children failed" << endl;
    
    delete mother;
    
    Children * son = new Children("Son who pretend to be a my father");
    if(son->TryParseTo<Parents>(&parsedParent))
        parsedParent->Speak();
    else
        cout << "Children parse to Parents failed" << endl;
    
    if(son->TryParseTo<Stranger>(&parsedStranger))
        parsedStranger->Speak();
    else
        cout << "Children parse to Stranger failed" << endl;
    
    delete son;
    
    //pointer of child class could pointer to base class object
    
    /* 
    * son = new Parents("Father who pretend to be a my son");
    if(son->TryParseTo<Parents>(&parsedParent))
        parsedParent->Speak();
    else
        cout << "Parse failed" << endl;
    
    delete son;
    */

    return 0;
}

/********************* Result *********************/

//Parents parse to Children failed
//    I am Daughter who pretend to be a my mother, I love my parents.
//    I am Son who pretend to be a my father, I love my parents.
//Children parse to Stranger failed
  
  

這段代碼中使用到的類跟dynamic_cast一文中使用的基本一樣,只是在基類中多了一個模板方法:


    template<typename T> bool TryParseTo(T** outValue)
    {
        T* temp = dynamic_cast<T*> (this);
        if (temp == NULL) return false;
        
        *outValue = temp;
        return true;
    }
    

該方法需要指定的目標類型,將自身指針轉換成目標指針。轉換成功,則將結果賦值給相應的變量,並返回真值;若失敗則返回假值,不改變指針變量。因爲要讓外部的指針變量能夠接受到改值,因此不得不使用指向指針的指針。

因爲在基類中以公共結果的形式出現,所以每一個子類都繼承了該方法,無論是基類的對象還是子類的對象都可以調用該方法。而該方法又不是虛方法,因此不併不希望子類去修改它。只是因爲方法是模板方法,可能在編譯的時候需要多花一些時間。

由於引用必須在定義時就賦值,並且dynamic_cast對於基於引用的轉換不成功時將拋出異常,因此對於基於引用的轉換,我還沒有想出有什麼好的山寨形式。

從測試代碼的結果也可以看出,對於該發放的調用都是成功有效的。

所以又應了導師常說的一句話:When use C++, the good news is that you can do everything you want, the bad news is that you have to do everything you want.


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