## polymorphic_cast
### 頭文件: `"boost/cast.hpp"`
C++中的多態轉型是用 `dynamic_cast`來實現的。`dynamic_cast`有一個有時會導致錯誤代碼的特性,那就是它對于所使用的不同類型會有不同的行為。在用于一個引用類型時,如果轉型失敗,`dynamic_cast` 會拋出一個`std::bad_cast`異常。這樣做的原因很簡單,因為C++里不允許有空的引用,所以要么轉型成功,要么轉型失敗而你獲得一個異常。當然,在 `dynamic_cast` 用于一個指針類型時,失敗時將返回空指針。
`dynamic_cast`的這種對指針和引用類型的不同行為以前被認為是一個有用 的特性,因為它允許程序員表達他們的意圖。典型地,如果轉型失敗不是一種邏輯錯誤,就使用指針轉型,如果它確是一種錯誤,就使用引用轉型。不幸的是,兩種 方法之間的區別僅在于一個*號和一個&號,這種細微的差別是不自然的。如果想把指針轉型失敗作為錯誤處理,該怎么辦?為了通過自動拋出異常來清楚 地表達這一點,也為了讓代碼更一致,Boost提供了`polymorphic_cast`. 它在轉型失敗時總是拋出一個 `std::bad_cast` 異常。
在《The C++ Programming Language 3rd Edition》中,Stroustrup對于指針類型的`dynamic_cast`說了以下一段話,事實是它可以返回空指針:
"偶爾可能會不小心忘了測試指針是否為空。如果這困擾了你,你可以寫一轉型函數在轉型失敗時拋出異常。"
`polymorphic_cast` 正是這樣一個轉型函數。
### 用法
`polymorphic_cast` 的用法類似于 `dynamic_cast`, 除了 (正是它的意圖) 在轉型失敗時總是拋出一個 `std::bad_cast` 異常。`polymorphic_cast` 的另一個特點是它是一個函數,必要時可以被重載。作為對我們的C++詞匯表的一個自然擴展,它使得代碼更清晰,類型轉換也更少錯誤。要使用它,就要包含頭文件`"boost/cast.hpp"`. 這個函數泛化了要轉換的類型,并接受一個要進行轉型的參數。
```
template <class Target, class Source>
polymorphic_cast(Source* p);
```
要注意的是,`polymorphic_cast` 沒有針對引用類型的版本。原因是那是`dynamic_cast`已經實現了的,沒有必須讓 `polymorphic_cast` 重復C++語言中已有的功能。以下例子示范了與 `dynamic_cast`類似的語法。
### 向下轉型和交叉轉型
使用[`dynamic_cast`](../Text/content.html#ch02lev2sec4) 或 [polymorphic_cast](../Text/content.html#ch02lev1sec2)可能有兩種典型的情況:從基類向派生類的向下轉型,或者交叉轉型,即從一個基類到另一個基類。以下例子示范了使用`polymorphic_cast`的兩類轉型。這里有兩個基類,`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` 是如何工作的,首先我們創建一個 `derived` 的實例,然后通過不同的基類指針以及派生類指針來操作它。對`p1`使用的第一個函數是`print`, 它是`base1` 和 `derived`的一個虛擬函數。我們還使用了向下轉型,以便可以調用 `only_here`, 它僅在 `derived`中可用:
```
derived* pD=boost::polymorphic_cast<derived*>(p1);
pD->only_here();
```
注意,如果 `polymorphic_cast` 失敗了,將拋出一個 `std::bad_cast` 異常,因此這段代碼被保護在一個 `try`/`catch` 塊中。這種做法與使用引用類型的`dynamic_cast`正好是一樣的。指針 `pD` 隨后被用來調用函數 `only_base2`. 這個函數是`base2`中的非虛擬函數,但是在`derived`中也提供了,因此隱藏了`base2`中的版本。因而我們需要執行一個交叉轉型來獲得一個`base2`指針,才可以調用到 `base2::only_base2` 而不是 `derived::only_base2`.
```
base2* pB=boost::polymorphic_cast<base2*>(p1);
pB->only_base2();
```
再一次,如果轉型失敗,將會拋出異常。這個例子示范了如果轉型失敗被認為是錯誤的話,使用`polymorphic_cast`可以多容易地進行錯誤處理。不需要測試空指針,也不會把錯誤傳播到函數以外。正如我們即將看到的,[`dynamic_cast`](#ch02lev2sec4) 有時會為這類代碼增加不必要的復雜性;它還可能導致未定義行為。
### dynamic_cast 對 polymorphic_cast
為了看一下這兩種轉型方法之間的不同,\[3\] ?我們把它們放在一起來比較一下復雜性。我們將重用前面例子中的類 `base1`, `base2`, 和 `derived`。你會發現在對指針類型使用`dynamic_cast`時,測試指針的有效性是一種既乏味又反復的事情,這使得測試很容易被緊張的程序員所忽略掉。
> \[3\] 技術上,`dynamic_cast` 是轉型操作符,而 `polymorphic_cast` 是函數模板。
```
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;
}
```
這兩個函數,`polymorphic_cast_example` 和 `dynamic_cast_example`, 使用不同的方法完成相同的工作。差別在于無論何時對指針使用 `dynamic_cast` ,我們都要記住測試返回的指針是否為空。在我們的例子里,這種情況被認為是錯誤的,因此要拋出一個類型為 `bad_cast` 的異常。\[4\] 如果使用 `polymorphic_cast`, 錯誤的處理被局限在`std::bad_cast`的異常處理例程中, 這意味著我們不需要為測試轉型的返回值而操心。在這個簡單的例子中,不難記住要測試返回指針的有效性,但還是要比使用`polymorphic_cast`做更多的工作。如果是幾百行的代碼,再加上兩三個程序員來維護這個函數的話,忘記測試或者拋出了錯誤的異常的風險就會大大增加。
> \[4\] 當然,返回指針無論如何都必須被檢查,除非你絕對肯定轉型不會失敗。
### polymorphic_cast 不總是正確的選擇
如果說失敗的指針轉型不應被視為錯誤,你就應該使用 `dynamic_cast` 而不是 `polymorphic_cast`. 例如,一種常見的情形是使用 `dynamic_cast` 來進行類型確定測試。使用異常處理來進行幾種類型的轉換測試是低效的,代碼也很難看。這種情形下 `dynamic_cast` 就很有用了。當我們同時使用 `polymorphic_cast` 和 `dynamic_cast`時,你應該非常清楚你自己的意圖。即使沒有 `polymorphic_cast`, 如果人們知道使用`dynamic_cast`的方法,他仍然可以達到相同的安全性,如下例所示。
```
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\] 并被轉型為 `some_other_class`的引用。這調用了`dynamic_cast`的異常拋出版本。例子中的第二部分使用了不會拋出異常的版本來轉型到指針類型。你是否認為這是清晰、簡明的代碼,答案取決于你的經驗。經驗豐富的C++程序員會非常明白這段程序。是不是所有看到這段代碼的人都十分熟悉`dynamic_cast`呢,或者他們不知道`dynamic_cast`的 行為要取決于進行轉型的是指針還是引用呢?你或者一個維護程序員是否總能記得對空指針進行測試?維護代碼的程序員是否知道要對指針進行解引用才可以在轉型 失敗時獲得異常?你真的想在每次你需要這樣的行為時都寫相同的邏輯嗎?抱歉說了這么多,這只是想表明,如果轉型失敗應該要拋出異常,那么 `polymorphic_cast` 要比 `dynamic_cast` 更堅固也更清晰。它要么成功,產生一個有效的指針,要么失敗,拋出一個異常。簡單的規則總是更容易被記住。
> \[5\] 如果指針 `p` 為空,該例將導致未定義行為,因為它解引用了一個空指針。
我們還沒有看到如何通過重載 `polymorphic_cast` 來解決一些不常見的轉型需求,但你應該知道這是可能的。何時你會想改變多態轉型的缺省行為呢?有一種情形是句柄/實體類(handle/body-classes), 向下轉型的規則可能會與缺省的不同,或者是根本不允許。
### 總結
必須記住,其它人將要維護我們寫的代碼。這意味著我們必須確保代碼以及它的意圖是清晰并且易懂的。這一點可以通過注釋部分地解決,但對于任何人,更容易的方法是不需加以說明的代碼。當(指針)轉型失敗被認為是異常時,`polymorphic_cast` 比`dynamic_cast`更能清晰地表明代碼的意圖,它也導致更短的代碼。如果轉型失敗不應被認為是錯誤,則應該使用`dynamic_cast`,這使得`dynamic_cast`的使用更為清楚。僅僅使用 `dynamic_cast` 來表明兩種不同的意圖很容易出錯,而不夠清楚。拋出異常與不拋出異常這兩個不同的版本對于大多數程序員而言太微妙了。
何時使用 `polymorphic_cast` 和 `dynamic_cast`:
* 當一個多態轉型的失敗是預期的時候,使用 `dynamic_cast<T*>`. 它清楚地表明轉型失敗不是一種錯誤。
* 當一個多態轉型必須成功以確保邏輯的正確性時,使用 `polymorphic_cast<T*>`. 它清楚地表明轉型失敗是一種錯誤。?
* 對引用類型執行多態轉型時,使用 `dynamic_cast`.
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結