索引目錄
傳統轉換方式及用戶自定義轉換
const_cast
reinterpret_cast
static_cast
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#中有很多簡潔實用的轉換方法,比如從字符串到數值類型的Parse和TryParse,還有包含了各種從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對於基於引用的轉換不成功時將拋出異常,因此對於基於引用的轉換,我還沒有想出有什麼好的山寨形式。
從測試代碼的結果也可以看出,對於該發放的調用都是成功有效的。