你的自定義類型所支持的所有字面轉(zhuǎn)換
numeric_cast
頭文件: "boost/cast.hpp"
整數(shù)類型間的轉(zhuǎn)換經(jīng)常會產(chǎn)生意外的結(jié)果,。例如,,long 可以擁有比short更大范圍的值,那么當(dāng)從 long 賦值到short 并且 long的數(shù)值超出了 short的范圍時會發(fā)生什么,?答案是結(jié)果是由實現(xiàn)定義的(比"你不可能明確知道"好聽一點的說法),。相同大小整數(shù)間的有符號數(shù)到無符號數(shù)的轉(zhuǎn)換是好的,只要有符號數(shù)的數(shù)值是正的,,但如果有符號數(shù)的數(shù)值是負(fù)的呢,?它將被轉(zhuǎn)換為一個大的無符號數(shù),如果這不是你的真實意圖,,那么就真的是一個問題了,。numeric_cast通過測試范圍是否合理來確保轉(zhuǎn)換的有效性,,當(dāng)范圍超出時它會拋出異常。
在我們?nèi)嬲J(rèn)識 numeric_cast之前,,我們必須弄清楚支配整數(shù)類型的轉(zhuǎn)換及提升的規(guī)則,。規(guī)則有很多并有時很微妙,即使是經(jīng)驗豐富的程序員也會被它們欺騙,。與其寫出所有這些規(guī)則[7]并展開它們,,我更愿意給出一些有關(guān)轉(zhuǎn)換的例子,它們會引起未定義或令人驚訝的行為,,然后再解釋所使用的轉(zhuǎn)換規(guī)則,。
[7]. C++標(biāo)準(zhǔn)在?4.5-4.9中討論數(shù)字類型的提升及轉(zhuǎn)換。
當(dāng)從一種數(shù)字類型賦值給另一種數(shù)字類型的變量時,,就會發(fā)生類型轉(zhuǎn)換。在目標(biāo)類型可以保存源類型的所有數(shù)值的情況下,,這種轉(zhuǎn)換是完全安全的,,否則就是不安全的。例如,,char 通常不能保存int的最大值,,所以當(dāng)從int到char的賦值發(fā)生時,很大可能int的值不能被表示為char. 當(dāng)類型可以表示的數(shù)值范圍不同時,,我們必須確認(rèn)用于轉(zhuǎn)換的實際數(shù)值在目標(biāo)類型的有效范圍之內(nèi),。否則,我們就會進(jìn)入實現(xiàn)定義行為的范疇,;那就是在把一個超出數(shù)字類型可能的數(shù)值范圍的值賦給這個數(shù)字類型時會發(fā)生的事情,。[8] 實現(xiàn)定義行為意味著具體實現(xiàn)可以自由地做任何它想做的;不同的系統(tǒng)可能有完全不同的行為,。numeric_cast 可以確保轉(zhuǎn)換是有效的,、合法的,否則就不允許轉(zhuǎn)換,。
[8] 無符號數(shù)也算,,盡管它的行為是有定義的。
用法
numeric_cast 是一個看起來象C++的轉(zhuǎn)型操作符的函數(shù)模板,,它泛化了目標(biāo)類型及源類型,。源類型可以從函數(shù)的參數(shù)隱式推導(dǎo)得到。使用numeric_cast, 要包含頭文件"boost/cast.hpp",。以下兩個轉(zhuǎn)換使用 numeric_cast 安全地將 int 轉(zhuǎn)換為 char, 以及將 double 轉(zhuǎn)換為 float.
char c=boost::numeric_cast<char>(12);
float f=boost::numeric_cast<float>(3.001);
一個最常見的數(shù)字轉(zhuǎn)換問題是將來自一個更寬范圍的值賦給范圍較窄的類型,。我們來看看numeric_cast如何幫忙。
從較大的類型到較小類型的賦值
從較大的類型(例如long)向較小的類型(例如short)賦值,,有可能數(shù)值過大或過小而不能被目標(biāo)類型所表示,。如果這發(fā)生了,,結(jié)果是(是的,正如你猜到的)實現(xiàn)所定義的,。我們稍后將討論無符號類型的潛在問題,;我們先從有符號類型開始。C++中有四個內(nèi)建的有符號類型:
signed char
short int (short)
int
long int (long)
沒有人可以絕對肯定哪個類型比其它的大[9],,但典型地,,上面的列表是按大小遞增的,除了 int 和 long 通常具有相同的值范圍,。但它們都是獨立的類型,,即使是有相同的大小。想查看你的系統(tǒng)上的類型大小,,可以使用 sizeof(T) 或 std::numeric_limits<T>::max() 和 std::numeric_limits<T>::min().
[9] 當(dāng)然,,有符號類型與無符號類型的范圍是不同的,即使它們有相同的大小,。
當(dāng)把一個有符號整數(shù)類型賦給另一個時,,C++標(biāo)準(zhǔn)說:
"若目標(biāo)類型為有符號類型,在數(shù)值可以被目標(biāo)類型表示時,,值不改變,;否則,值為實現(xiàn)定義,。"[10]
[10] 見C++標(biāo)準(zhǔn) ?4.7.3
以下代碼段示范了看起來象是正確的賦值是如何導(dǎo)致實現(xiàn)定義的數(shù)值,,最后看看如何通過numeric_cast的幫助避免它們。
#include <iostream>
#include "boost/cast.hpp"
#include "boost/limits.hpp"
int main() {
std::cout << "larger_to_smaller example\n";
// 沒有使用numeric_cast的轉(zhuǎn)換
long l=std::numeric_limits<short>::max();
short s=l;
std::cout << "s is: " << s << '\n';
s=++l;
std::cout << "s is: " << s << "\n\n";
// 使用numeric_cast的轉(zhuǎn)換
try {
l=std::numeric_limits<short>::max();
s=boost::numeric_cast<short>(l);
std::cout << "s is: " << s << '\n';
s=boost::numeric_cast<short>(++l);
std::cout << "s is: " << s << '\n';
}
catch(boost::bad_numeric_cast& e) {
std::cout << e.what() << '\n';
}
}
通過使用 std::numeric_limits, long l 被初始化 short 可以表示的最大值,。該值被賦給 short s 并輸出,。然后,l 被加一,,這意味著它的值不能再被short所表示,;它超出了 short 所能表示的范圍。把 l 的新值賦給 s, s 再次被輸出,。你可能要問輸出的值是什么,?好的,因為賦值的結(jié)果屬于實現(xiàn)定義的行為,,這取決于你使用的平臺,。在我的系統(tǒng)中,使用我的編譯器,,它變成了一個大的負(fù)值,,即它被回繞了。必須運行前面的代碼才知道在你的系統(tǒng)中會有什么結(jié)果[11]。接著,,再次執(zhí)行相同的操作,,但這次用了 numeric_cast. 第一個轉(zhuǎn)型成功了,因為數(shù)值在范圍之內(nèi),。而第二個轉(zhuǎn)型卻會失敗,,結(jié)果是拋出一個bad_numeric_cast 異常。程序的輸出如下,。
[11] 這種行為和結(jié)果在32位平臺上十分常見,。
larger_to_smaller example
s is: 32767
s is: -32768
s is: 32767
bad numeric cast: loss of range in numeric_cast
比避開實現(xiàn)定義行為更為重要的是,numeric_cast 幫助我們避免了錯誤,,否則會很難捕捉到這些錯誤,。那個奇怪的數(shù)值可能被傳送到應(yīng)用程序的其它部分,程序可能會繼續(xù)工作,,但幾乎可以肯定將產(chǎn)生錯誤的結(jié)果,。當(dāng)然,這僅對于特定的數(shù)值會發(fā)生這樣的情況,,如果這些數(shù)值很少出現(xiàn),,那么錯誤將很難被發(fā)現(xiàn)。這種錯誤非常陰險,,因為它們僅僅對某些特定值會發(fā)生,而不是總會發(fā)生,。
精寬或取值范圍的損失并不常見,,如果你不確定一個值對于目標(biāo)類型是否過大或過小,numeric_cast 就是你可以使用的工具,。你甚至可以在不需要的時候使用 numeric_cast ,;維護(hù)的程序員可能沒有象你一樣的洞察力。注意,,雖然我們在這里只討論了有符號類型,,但同樣的原理可應(yīng)用于于無符號類型。
特殊情況:目標(biāo)類型為無符號整數(shù)
無符號整數(shù)類型有一個非常有趣的特性,,任何數(shù)值都有可以合法地賦給它們,!對于無符號類型而言,無所謂正或負(fù)的溢出,。數(shù)值被簡單地對目標(biāo)類型最大值加一取模,。什么意思?看看以下例子會更清楚一些,。
#include <iostream>
#include "boost/limits.hpp"
int main() {
unsigned char c;
long l=std::numeric_limits<unsigned char>::max()+14;
c=l;
std::cout << "c is: " << (int)c << '\n';
long reduced=l%(std::numeric_limits<unsigned char>::max()+1);
std::cout << "reduced is: " << reduced << '\n';
}
運行這個程序的輸出如下:
c is: 13
reduced is: 13
這個例子把一個明顯超出unsigned char可以表示的數(shù)值賦給它,,然后再計算得到同樣的數(shù)值。賦值的動作可以用這一行代碼來示范:
long reduced=l%(std::numeric_limits<unsigned char>::max()+1);
這種行為通常被稱為數(shù)值回繞(value wrapping)。如果你想用這個特性,,就沒有必要在這種情況下使用 numeric_cast,。此外,numeric_cast 也不接受它,。numeric_cast的意圖是捕捉錯誤,,而錯誤應(yīng)該是因為用戶的誤解而引起的。如果目標(biāo)類型不能表示賦給它的數(shù)值,,就拋出一個 bad_numeric_cast 異常,。因為無符號整數(shù)的算法是明確定義的,不會引起程序員的重大錯誤[12],。對于 numeric_cast, 重要的是確保獲得實際的數(shù)值,。
[12] 觀點是:如果你真的想要數(shù)值回繞,就不要使用 numeric_cast.
有符號和無符號整數(shù)類型的混用
混用有符號和無符號類型可能很有趣[13],,特別是執(zhí)行算術(shù)操作時,。普通的賦值也會產(chǎn)生微妙的問題。最常見的問題是將一個負(fù)值賦給無符號類型,。結(jié)果幾乎可以肯定不是你原來的意圖,。另一種情形是從無符號類型到同樣大小的有稱號類型的賦值。不知什么原因,,人們總是會很容易忘記無符號類型可以持有比同樣大小的有符號類型更大的值,。特別是在表達(dá)式或函數(shù)調(diào)用中更容易忘記。以下例子示范了如何通過numeric_cast來捕捉這種常見的錯誤,。
[13] 當(dāng)然這是一個高度主觀的問題,,你的觀點可能不同。
#include <iostream>
#include "boost/limits.hpp"
#include "boost/cast.hpp"
int main() {
unsigned int ui=std::numeric_limits<unsigned int>::max();
int i;
try {
std::cout << "Assignment from unsigned int to signed int\n";
i=boost::numeric_cast<int>(ui);
}
catch(boost::bad_numeric_cast& e) {
std::cout << e.what() << "\n\n";
}
try {
std::cout << "Assignment from signed int to unsigned int\n";
i=-12;
ui=boost::numeric_cast<unsigned int>(i);
}
catch(boost::bad_numeric_cast& e) {
std::cout << e.what() << "\n\n";
}
}
輸出清晰地表明了預(yù)期的錯誤,。
Assignment from unsigned int to signed int
bad numeric cast: loss of range in numeric_cast
Assignment from signed int to unsigned int
bad numeric cast: loss of range in numeric_cast
基本的規(guī)則很簡單:無論何時在不同的類型間執(zhí)行類型轉(zhuǎn)換,,都應(yīng)該使用 numeric_cast來保證轉(zhuǎn)換的安全。
浮點數(shù)類型
numeric_cast 不能幫助我們在浮點數(shù)間的轉(zhuǎn)換中避免精度的損失,。原因是float, double, 和 long double間的轉(zhuǎn)換不象整數(shù)類型間的隱式轉(zhuǎn)換那樣敏感,。記住這點很重要,因為你可能會認(rèn)為以下代碼應(yīng)該拋出異常,。
double d=0.123456789123456;
float f=0.123456;
try {
f=boost::numeric_cast<float>(d);
}
catch(boost::bad_numeric_cast& e) {
std::cout << e.what();
}
運行這段代碼不會有異常拋出,。在許多實現(xiàn)中,從 double 到 float 的轉(zhuǎn)換都會導(dǎo)致精度的損失,,雖然C++標(biāo)準(zhǔn)沒有保證會這樣,。我們所能知道的就是,double 至少具有 float 的精度,。
從浮點數(shù)類型轉(zhuǎn)為整數(shù)類型又會怎樣呢,?當(dāng)一個浮點數(shù)類型被轉(zhuǎn)換為一個整數(shù)類型,它會被截斷;小數(shù)部分會被扔掉,。numeric_cast 對截斷后的數(shù)值與目標(biāo)類型進(jìn)行相同的檢查,,就象在兩個整數(shù)類型間的檢查一樣。
double d=127.123456789123456;
char c;
std::cout << "char type maximum: ";
std::cout << (int)std::numeric_limits<char>::max() << "\n\n";
c=d;
std::cout << "Assignment from double to char: \n";
std::cout << "double: " << d << "\n";
std::cout << "char: " << (int)c << "\n";
std::cout << "Trying the same thing with numeric_cast:\n";
try {
c=boost::numeric_cast<char>(d);
std::cout << "double: " << d;
std::cout << "char: " << (int)c;
}
catch(boost::bad_numeric_cast& e) {
std::cout << e.what();
}
象前面的代碼那樣進(jìn)行范圍檢查以確保有效的賦值是一件令人畏縮的工作,。雖然規(guī)則看起來很簡單,,但是有很多組合要被考慮。例如,,測試從浮點數(shù)到整數(shù)的代碼看起來就象這樣:
template <typename INT, typename FLOAT>
bool is_valid_assignment(FLOAT f) {
return std::numeric_limits<INT>::max() >=
static_cast<INT>(f);
}
盡管我已經(jīng)提起過在一個浮點數(shù)類型被轉(zhuǎn)換時,,小數(shù)部分會被丟棄,在這個實現(xiàn)中還得很容易忽略這個錯誤,。這對于算術(shù)類型的轉(zhuǎn)換和提升是自然的,。去掉static_cast 就可以正確地測試,因為這樣 numeric_limits<INT>::max 的結(jié)果會被轉(zhuǎn)換為浮點數(shù)類型[14],。如果是浮點數(shù)類型轉(zhuǎn)為整數(shù)類型,,它會被截斷;換句話說,,這個函數(shù)的問題在于丟失了小數(shù)部分,。
[14] 這是正常的算術(shù)轉(zhuǎn)換結(jié)果。
總結(jié)
numeric_cast 提供了算術(shù)類型間高效的范圍檢查轉(zhuǎn)換,。在目標(biāo)類型可以持有所有源類型的值時,,使用 numeric_cast沒有額外的效率代價。它只在目標(biāo)類型僅能表示源類型的值的子集時有影響,。當(dāng)轉(zhuǎn)換失敗時,,numeric_cast 通過拋出一個 bad_numeric_cast異常來表示失敗。對于數(shù)值類型間的轉(zhuǎn)換有很多復(fù)雜的規(guī)則,,確保轉(zhuǎn)換的正確性是很重要的。
以下情況時使用 numeric_cast:
在這里注意到一個模式了嗎,?模仿已有的語言和庫的名字及行為是簡化學(xué)習(xí)及使用的好方法,但也需要仔細(xì)地考慮,。增加內(nèi)建的C++轉(zhuǎn)型就象沿著狹窄的小路行走,;一旦迷路會帶來很高的代價。遵循語言的語法及語義規(guī)則才是負(fù)責(zé)任的,。事實上,,對于初學(xué)者,內(nèi)建的轉(zhuǎn)型操作符與看起來象轉(zhuǎn)型操作符的函數(shù)可能并沒有不同,,所以如果行為錯誤將會導(dǎo)致災(zāi)難,。numeric_cast 有著與 static_cast, dynamic_cast, 和 reinterpret_cast類似的語法和語義。如果它看起來和用起來象轉(zhuǎn)型操作,它就是轉(zhuǎn)型操作,,是對轉(zhuǎn)型操作的一個良好的擴(kuò)展
polymorphic_cast
頭文件: "boost/cast.hpp"
C++中的多態(tài)轉(zhuǎn)型是用 dynamic_cast來實現(xiàn)的,。dynamic_cast有一個有時會導(dǎo)致錯誤代碼的特性,那就是它對于所使用的不同類型會有不同的行為,。在用于一個引用類型時,,如果轉(zhuǎn)型失敗,dynamic_cast 會拋出一個std::bad_cast異常,。這樣做的原因很簡單,,因為C++里不允許有空的引用,所以要么轉(zhuǎn)型成功,,要么轉(zhuǎn)型失敗而你獲得一個異常,。當(dāng)然,在 dynamic_cast 用于一個指針類型時,,失敗時將返回空指針,。
dynamic_cast的這種對指針和引用類型的不同行為以前被認(rèn)為是一個有用的特性,因為它允許程序員表達(dá)他們的意圖,。典型地,,如果轉(zhuǎn)型失敗不是一種邏輯錯誤,就使用指針轉(zhuǎn)型,,如果它確是一種錯誤,,就使用引用轉(zhuǎn)型。不幸的是,,兩種方法之間的區(qū)別僅在于一個*號和一個&號,,這種細(xì)微的差別是不自然的。如果想把指針轉(zhuǎn)型失敗作為錯誤處理,,該怎么辦,?為了通過自動拋出異常來清楚地表達(dá)這一點,也為了讓代碼更一致,,Boost提供了polymorphic_cast. 它在轉(zhuǎn)型失敗時總是拋出一個 std::bad_cast 異常,。
在《The C++ Programming Language 3rd Edition》中,Stroustrup對于指針類型的dynamic_cast說了以下一段話,,事實是它可以返回空指針:
"偶爾可能會不小心忘了測試指針是否為空,。如果這困擾了你,你可以寫一轉(zhuǎn)型函數(shù)在轉(zhuǎn)型失敗時拋出異常,。"
polymorphic_cast 正是這樣一個轉(zhuǎn)型函數(shù),。
用法
polymorphic_cast 的用法類似于 dynamic_cast, 除了 (正是它的意圖) 在轉(zhuǎn)型失敗時總是拋出一個 std::bad_cast 異常。polymorphic_cast 的另一個特點是它是一個函數(shù),,必要時可以被重載,。作為對我們的C++詞匯表的一個自然擴(kuò)展,,它使得代碼更清晰,類型轉(zhuǎn)換也更少錯誤,。要使用它,,就要包含頭文件"boost/cast.hpp". 這個函數(shù)泛化了要轉(zhuǎn)換的類型,并接受一個要進(jìn)行轉(zhuǎn)型的參數(shù),。
template <class Target, class Source>
polymorphic_cast(Source* p);
要注意的是,,polymorphic_cast 沒有針對引用類型的版本。原因是那是dynamic_cast已經(jīng)實現(xiàn)了的,,沒有必須讓 polymorphic_cast 重復(fù)C++語言中已有的功能,。以下例子示范了與 dynamic_cast類似的語法。
向下轉(zhuǎn)型和交叉轉(zhuǎn)型
使用dynamic_cast 或 polymorphic_cast可能有兩種典型的情況:從基類向派生類的向下轉(zhuǎn)型,,或者交叉轉(zhuǎn)型,,即從一個基類到另一個基類。以下例子示范了使用polymorphic_cast的兩類轉(zhuǎn)型,。這里有兩個基類,,base1 和 base2, 以及一個從兩個基類公有派生而來的類 derived 。
#include <iostream>
#include <string>
#include "boost/cast.hpp"
class base1 {
public:
virtual void print() {
std::cout << "base1::print()\n";
}
virtual ~base1() {}
};
class base2 {
public:
void only_base2() {
std::cout << "only_base2()\n";
}
virtual ~base2() {}
};
class derived : public base1, public base2 {
public:
void print() {
std::cout << "derived::print()\n";
}
void only_here() {
std::cout << "derived::only_here()\n";
}
void only_base2() {
std::cout << "Oops, here too!\n";
}
};
int main() {
base1* p1=new derived;
p1->print();
try {
derived* pD=boost::polymorphic_cast<derived*>(p1);
pD->only_here();
pD->only_base2();
base2* pB=boost::polymorphic_cast<base2*>(p1);
pB->only_base2();
}
catch(std::bad_cast& e) {
std::cout << e.what() << '\n';
}
delete p1;
}
我們來看看 polymorphic_cast 是如何工作的,,首先我們創(chuàng)建一個 derived 的實例,,然后通過不同的基類指針以及派生類指針來操作它。對p1使用的第一個函數(shù)是print, 它是base1 和 derived的一個虛擬函數(shù),。我們還使用了向下轉(zhuǎn)型,,以便可以調(diào)用 only_here, 它僅在 derived中可用:
derived* pD=boost::polymorphic_cast<derived*>(p1);
pD->only_here();
注意,如果 polymorphic_cast 失敗了,,將拋出一個 std::bad_cast 異常,,因此這段代碼被保護(hù)在一個 try/catch 塊中。這種做法與使用引用類型的dynamic_cast正好是一樣的,。指針 pD 隨后被用來調(diào)用函數(shù) only_base2. 這個函數(shù)是base2中的非虛擬函數(shù),,但是在derived中也提供了,因此隱藏了base2中的版本,。因而我們需要執(zhí)行一個交叉轉(zhuǎn)型來獲得一個base2指針,,才可以調(diào)用到 base2::only_base2 而不是 derived::only_base2.
base2* pB=boost::polymorphic_cast<base2*>(p1);
pB->only_base2();
再一次,如果轉(zhuǎn)型失敗,,將會拋出異常,。這個例子示范了如果轉(zhuǎn)型失敗被認(rèn)為是錯誤的話,,使用polymorphic_cast可以多容易地進(jìn)行錯誤處理,。不需要測試空指針,也不會把錯誤傳播到函數(shù)以外,。正如我們即將看到的,,dynamic_cast 有時會為這類代碼增加不必要的復(fù)雜性,;它還可能導(dǎo)致未定義行為。
dynamic_cast 對 polymorphic_cast
為了看一下這兩種轉(zhuǎn)型方法之間的不同,,[3] 我們把它們放在一起來比較一下復(fù)雜性,。我們將重用前面例子中的類 base1, base2, 和 derived。你會發(fā)現(xiàn)在對指針類型使用dynamic_cast時,,測試指針的有效性是一種既乏味又反復(fù)的事情,,這使得測試很容易被緊張的程序員所忽略掉。
[3] 技術(shù)上,,dynamic_cast 是轉(zhuǎn)型操作符,,而 polymorphic_cast 是函數(shù)模板。
void polymorphic_cast_example(base1* p) {
derived* pD=boost::polymorphic_cast<derived*>(p);
pD->print();
base2* pB=boost::polymorphic_cast<base2*>(p);
pB->only_base2();
}
void dynamic_cast_example(base1* p) {
derived* pD=dynamic_cast<derived*>(p);
if (!pD)
throw std::bad_cast();
pD->print();
base2* pB=dynamic_cast<base2*>(p);
if (!pB)
throw std::bad_cast();
pB->only_base2();
}
int main() {
base1* p=new derived;
try {
polymorphic_cast_example(p);
dynamic_cast_example(p);
}
catch(std::bad_cast& e) {
std::cout << e.what() << '\n';
}
delete p;
}
這兩個函數(shù),,polymorphic_cast_example 和 dynamic_cast_example, 使用不同的方法完成相同的工作,。差別在于無論何時對指針使用 dynamic_cast ,我們都要記住測試返回的指針是否為空,。在我們的例子里,,這種情況被認(rèn)為是錯誤的,因此要拋出一個類型為 bad_cast 的異常,。[4] 如果使用polymorphic_cast, 錯誤的處理被局限在std::bad_cast的異常處理例程中,, 這意味著我們不需要為測試轉(zhuǎn)型的返回值而操心。在這個簡單的例子中,,不難記住要測試返回指針的有效性,,但還是要比使用polymorphic_cast做更多的工作。如果是幾百行的代碼,,再加上兩三個程序員來維護(hù)這個函數(shù)的話,,忘記測試或者拋出了錯誤的異常的風(fēng)險就會大大增加。
[4] 當(dāng)然,,返回指針無論如何都必須被檢查,,除非你絕對肯定轉(zhuǎn)型不會失敗。
polymorphic_cast 不總是正確的選擇
如果說失敗的指針轉(zhuǎn)型不應(yīng)被視為錯誤,,你就應(yīng)該使用 dynamic_cast 而不是 polymorphic_cast. 例如,,一種常見的情形是使用 dynamic_cast 來進(jìn)行類型確定測試。使用異常處理來進(jìn)行幾種類型的轉(zhuǎn)換測試是低效的,,代碼也很難看,。這種情形下 dynamic_cast 就很有用了。當(dāng)我們同時使用polymorphic_cast 和 dynamic_cast時,,你應(yīng)該非常清楚你自己的意圖,。即使沒有 polymorphic_cast, 如果人們知道使用dynamic_cast的方法,他仍然可以達(dá)到相同的安全性,,如下例所示,。
void failure_is_error(base1* p) {
try {
some_other_class& soc=dynamic_cast<some_other_class&>(*p);
// 使用 soc
}
catch(std::bad_cast& e) {
std::cout << e.what() << '\n';
}
}
void failure_is_ok(base1* p) {
if (some_other_class* psoc=
dynamic_cast<some_other_class*>(p)) {
// 使用 psoc
}
}
在這個例子中,,指針 p 被解引用[5] 并被轉(zhuǎn)型為 some_other_class的引用。這調(diào)用了dynamic_cast的異常拋出版本,。例子中的第二部分使用了不會拋出異常的版本來轉(zhuǎn)型到指針類型,。你是否認(rèn)為這是清晰、簡明的代碼,,答案取決于你的經(jīng)驗,。經(jīng)驗豐富的C++程序員會非常明白這段程序。是不是所有看到這段代碼的人都十分熟悉dynamic_cast呢,,或者他們不知道dynamic_cast的行為要取決于進(jìn)行轉(zhuǎn)型的是指針還是引用呢,?你或者一個維護(hù)程序員是否總能記得對空指針進(jìn)行測試?維護(hù)代碼的程序員是否知道要對指針進(jìn)行解引用才可以在轉(zhuǎn)型失敗時獲得異常,?你真的想在每次你需要這樣的行為時都寫相同的邏輯嗎,?抱歉說了這么多,這只是想表明,,如果轉(zhuǎn)型失敗應(yīng)該要拋出異常,,那么 polymorphic_cast 要比 dynamic_cast 更堅固也更清晰。它要么成功,,產(chǎn)生一個有效的指針,,要么失敗,拋出一個異常,。簡單的規(guī)則總是更容易被記住,。
[5] 如果指針 p 為空,該例將導(dǎo)致未定義行為,,因為它解引用了一個空指針,。
我們還沒有看到如何通過重載 polymorphic_cast 來解決一些不常見的轉(zhuǎn)型需求,但你應(yīng)該知道這是可能的,。何時你會想改變多態(tài)轉(zhuǎn)型的缺省行為呢,?有一種情形是句柄/實體類(handle/body-classes), 向下轉(zhuǎn)型的規(guī)則可能會與缺省的不同,或者是根本不允許,。
總結(jié)
必須記住,,其它人將要維護(hù)我們寫的代碼。這意味著我們必須確保代碼以及它的意圖是清晰并且易懂的,。這一點可以通過注釋部分地解決,,但對于任何人,更容易的方法是不需加以說明的代碼,。當(dāng)(指針)轉(zhuǎn)型失敗被認(rèn)為是異常時,,polymorphic_cast 比dynamic_cast更能清晰地表明代碼的意圖,它也導(dǎo)致更短的代碼,。如果轉(zhuǎn)型失敗不應(yīng)被認(rèn)為是錯誤,,則應(yīng)該使用dynamic_cast,這使得dynamic_cast的使用更為清楚,。僅僅使用 dynamic_cast 來表明兩種不同的意圖很容易出錯,,而不夠清楚。拋出異常與不拋出異常這兩個不同的版本對于大多數(shù)程序員而言太微妙了,。
何時使用 polymorphic_cast 和 dynamic_cast:
當(dāng)一個多態(tài)轉(zhuǎn)型的失敗是預(yù)期的時候,,使用 dynamic_cast<T*>. 它清楚地表明轉(zhuǎn)型失敗不是一種錯誤。
當(dāng)一個多態(tài)轉(zhuǎn)型必須成功以確保邏輯的正確性時,,使用 polymorphic_cast<T*>. 它清楚地表明轉(zhuǎn)型失敗是一種錯誤,。
對引用類型執(zhí)行多態(tài)轉(zhuǎn)型時,使用 dynamic_cast.