1. 程式人生 > >C++“準”標準庫Boost學習指南(2):Boost.Conversion

C++“準”標準庫Boost學習指南(2):Boost.Conversion

原文地址

Conversion庫包含有一些函式,它們是現有的強制型別轉換操作符(static_cast, const_cast, 和 


dynamic_cast)的增強。Conversion為安全的多型轉換增加了 polymorphic_cast 和 polymorphic_downcast


,為安全的數字型別轉換增加了 numeric_cast,為文字轉換(如string 和 double間的轉換)增加 


lexical_cast。你可為了你自己的型別更好地工作而定製這些型別轉換,可能這些型別並不可以使用語言本


身所提供的型別轉換。


Conversion 庫如何改進你的程式?
可理解、可維護,以及一致的多型型別轉換
靜態向下轉型使用比static_cast更安全的結構
進行範圍判斷的數字轉換確保正確的值邏輯以及更少的除錯時間
正確且可重用的文字轉換導致更少的編碼時間


C++的多功能性是它獲得成功的主要原因之一,但有時也是麻煩的來源,因為語言各部分的複雜性。例如,數


字轉換規則以及型別提升規則都很複雜。其它轉換雖然簡單,但也很乏味;多少次我們需要寫一個安全的函


數來進行strings 和 ints, doubles 和 strings之間的轉換?在你寫的每個庫和程式裡,型別轉換都可能是


有問題的,這就是 Conversion 庫可以幫助你的地方。它提供了防止危險轉換及可複用的型別轉換工具。


Conversion 庫由四個轉換函式組成,分別提供了更好的型別安全性(polymorphic_cast), 更高效的型別安全


防護(polymorphic_downcast), 範圍檢查的數字轉換(numeric_cast), 以及文字轉換(lexical_cast)。這些


類cast函式共享C++轉型操作符的語義。與C++的轉型操作符一樣,這些函式具有一個重要的品質,型別安全


性,這是它們與C風格轉型的區別:它們明確無誤地表達了程式設計師的意圖。我們所寫的程式碼的重要性不僅在於


它可以正確執行。更重要的是程式碼可否清晰地表達我們的意圖。這個庫使得我們可以更容易地擴充套件我們的C++


詞彙表。
我們的意圖。這個庫使得我們可以更容易地擴充套件我們的C++詞彙表。


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 或 polymorphic_cast可能有兩種典型的情況:從基類向派生類的向下轉型,或者交叉轉


型,即從一個基類到另一個基類。以下例子示範了使用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 有時會為這類程式碼增加不必要的複雜性;它還可能導致未定義行為。


dynamic_cast 對 polymorphic_cast


為了看一下這兩種轉型方法之間的不同,我們把它們放在一起來比較一下複雜性。我們將重用前面例子中的


類 base1, base2, 和 derived。你會發現在對指標型別使用dynamic_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 的異常。如果使用 polymorphic_cast, 錯誤的處


理被侷限在std::bad_cast的異常處理例程中, 這意味著我們不需要為測試轉型的返回值而操心。在這個簡


單的例子中,不難記住要測試返回指標的有效性,但還是要比使用polymorphic_cast做更多的工作。如果是


幾百行的程式碼,再加上兩三個程式設計師來維護這個函式的話,忘記測試或者丟擲了錯誤的異常的風險就會大大


增加。


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 被解引用並被轉型為 some_other_class的引用。這呼叫了dynamic_cast的異常丟擲


版本。例子中的第二部分使用了不會丟擲異常的版本來轉型到指標型別。你是否認為這是清晰、簡明的程式碼


,答案取決於你的經驗。經驗豐富的C++程式設計師會非常明白這段程式。是不是所有看到這段程式碼的人都十分熟


悉dynamic_cast呢,或者他們不知道dynamic_cast的行為要取決於進行轉型的是指標還是引用呢?你或者一


個維護程式設計師是否總能記得對空指標進行測試?維護程式碼的程式設計師是否知道要對指標進行解引用才可以在轉


型失敗時獲得異常?你真的想在每次你需要這樣的行為時都寫相同的邏輯嗎?抱歉說了這麼多,這只是想表


明,如果轉型失敗應該要丟擲異常,那麼 polymorphic_cast 要比 dynamic_cast 更堅固也更清晰。它要麼


成功,產生一個有效的指標,要麼失敗,丟擲一個異常。簡單的規則總是更容易被記住。


我們還沒有看到如何通過過載 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.




polymorphic_downcast


標頭檔案: "boost/cast.hpp"


有時 dynamic_cast 被認為太過低效(的確如此)。執行dynamic_cast需要額外的執行時間。為了避免這些代


價,常常會誘使你使用 static_cast, 它沒有這些效能代價。static_cast 用於向下轉型可能在危險的,並


會導致錯誤,但它的確比dynamic_cast要快。如果這些加速是需要的,那我們就要確保向下轉型的安全性。


dynamic_cast 會測試向下轉型的結果,並在失敗時返回空指標或丟擲異常,而static_cast 則僅僅執行需要


的指標運算,並將保證轉型有效的責任留給了程式設計師。為了確保用 static_cast 進行向下轉型是安全的,你


必須確保對每次要執行的轉型進行測試。polymorphic_downcast 用dynamic_cast進行了轉型的測試,但僅是


在除錯模式下;然後它就使用 static_cast 去執行轉型。在釋出模式下,只執行 static_cast 。這樣的轉


型方法意味著你知道它不可能失敗,所以沒有錯誤處理,也沒有異常丟擲。那麼如果在非除錯模式下 


polymorphic_downcast 失敗了,會發生什麼呢?未定義的行為。你的計算機可能崩潰。地球可以停止自轉。


你可能飛到雲上。你唯一可以肯定的是你的程式可能會發生不好的事情。如果 polymorphic_downcast 是在


除錯模式下失敗的,它對dynamic_cast產生的空指標執行斷言(並退出)。


在討論用polymorphic_downcast更換dynamic_cast可以如何加速你的程式之前,你應該先檢查一下設計。轉


型的優化幾乎就代表著設計的問題。如果向下轉型真的是必須的,並且被證實是效能的瓶頸,


polymorphic_downcast 就是你需要的。你可以在測試時發現錯誤的轉型,而不是在產品中(釋出模式構建),


如果你曾經聽到過從電話另一端傳來的使用者的尖叫,你就該知道在測試時找出錯誤是多麼的重要,它使生活


更輕鬆。很有可能你就是使用者,而且知道發現並報告別人的錯誤是多麼的討厭。因此,在真正需要的時候才


用 polymorphic_downcast ,而且要小心。


用法
polymorphic_downcast 用於那些你應該用而又不想用dynamic_cast的情形,原因是你確認將要發生的轉型肯


定會成功,而且你需要提升它帶來的效能。注意:一定要確保使用的polymorphic_downcast所有可能的型別


及轉換組合都經過測試。否則,不要使用 polymorphic_downcast; 用 dynamic_cast 代替它。當你決定繼續


使用polymorphic_downcast, 包含標頭檔案"boost/cast.hpp".
#include <iostream>
#include "boost/cast.hpp"


struct base {
  virtual ~base() {};
};


struct derived1 : public base {
  void foo() {
    std::cout << "derived1::foo()\n";
  }
};


struct derived2 : public base {
  void foo() {
    std::cout << "derived2::foo()\n";
  }
};


void older(base* p) {
  // Logic that suggests that p points to derived1 omitted
  derived1* pd=static_cast<derived1*>(p);
  pd->foo(); // <-- What will happen here?
}


void newer(base* p) {
  // Logic that suggests that p points to derived1 omitted
  derived1* pd=boost::polymorphic_downcast<derived1*>(p);
  // ^-- The above cast will cause an assertion in debug builds
  pd->foo();
}


int main() {
       derived2* p=new derived2;
       older(p); // <-- Undefined
       newer(p); // <-- Well defined in debug build
}
複製程式碼
函式older中的static_cast 會編譯成功,但它會帶來壞運氣,成員函式foo的存在使得錯誤(可能有,但不保


證)被錯過,直到有人拿著一份錯誤報告,用偵錯程式在別的地方查詢奇怪的行為。當使用static_cast將指標


向下轉型為 derived1*, 編譯器沒有選擇,只能相信程式設計師,轉型是有效的。但事實上,傳送給older的指標


是指向一個derived2例項的。因此,older裡的指標 pd 指向了一個完全不同的型別,這意味著什麼都可能發


生。這就是使用static_cast進行向下轉型的風險。轉型總是"成功"的,但指標可能是無效的。


在對函式newer的呼叫裡,"更好的 static_cast," polymorphic_downcast 不僅捕捉到了錯誤,並且使用斷


言指出了發生錯誤的地方。當然,這僅在除錯模式下是真的,使用dynamic_cast來測試轉型是否成功。把一


個無效的轉型留在釋出版本中會導致不幸。換言之,就算你在除錯模式下獲得了額外的安全性,但這並不足


以代表你已經試過了所有可能的轉換。


總結


使用static_cast進行向下轉換通常是危險的。你不應該這樣做,但如果一定要,使用polymorphic_downcast


可以增加一點安全性。它在除錯模式下增加了測試,可以幫助你發現轉型的錯誤,但你必須測試所有可能的


轉型以確保它的安全使用。
如果你正在使用向下轉型並需要在釋出版本中獲得static_cast的速度,就用 polymorphic_downcast; 至少


在測試時你可以在出錯時得到斷言的幫助。
如果不能測試所有可能的轉型,就不要使用 polymorphic_downcast.






numeric_cast


標頭檔案: "boost/cast.hpp"


整數型別間的轉換經常會產生意外的結果。例如,long 可以擁有比short更大範圍的值,那麼當從 long 賦


值到short 並且 long的數值超出了 short的範圍時會發生什麼?答案是結果是由實現定義的(比"你不可能明


確知道"好聽一點的說法)。相同大小整數間的有符號數到無符號數的轉換是好的,只要有符號數的數值是正


的,但如果有符號數的數值是負的呢?它將被轉換為一個大的無符號數,如果這不是你的真實意圖,那麼就


真的是一個問題了。numeric_cast 通過測試範圍是否合理來確保轉換的有效性,當範圍超出時它會丟擲異常





在我們全面認識 numeric_cast之前,我們必須弄清楚支配整數型別的轉換及提升的規則。規則有很多並有時


很微妙,即使是經驗豐富的程式設計師也會被它們欺騙。與其寫出所有這些規則並展開它們,我更願意給出一些


有關轉換的例子,它們會引起未定義或令人驚訝的行為,然後再解釋所使用的轉換規則。 


當從一種數字型別賦值給另一種數字型別的變數時,就會發生型別轉換。在目標型別可以儲存源型別的所有


數值的情況下,這種轉換是完全安全的,否則就是不安全的。例如,char 通常不能儲存int的最大值,所以


當從int到char的賦值發生時,很大可能int的值不能被表示為char. 當型別可以表示的數值範圍不同時,我


們必須確認用於轉換的實際數值在目標型別的有效範圍之內。否則,我們就會進入實現定義行為的範疇;那


就是在把一個超出數字型別可能的數值範圍的值賦給這個數字型別時會發生的事情。實現定義行為意味著具


體實現可以自由地做任何它想做的;不同的系統可能有完全不同的行為。numeric_cast 可以確保轉換是有效


的、合法的,否則就不允許轉換。


用法


numeric_cast 是一個看起來象C++的轉型操作符的函式模板,它泛化了目標型別及源型別。源型別可以從函


數的引數隱式推導得到。使用numeric_cast, 要包含標頭檔案"boost/cast.hpp"。以下兩個轉換使用 


numeric_cast 安全地將 int 轉換為 char, 以及將 double 轉換為 float.
char c=boost::numeric_cast<char>(12);
float f=boost::numeric_cast<float>(3.001);
複製程式碼
一個最常見的數字轉換問題是將來自一個更寬範圍的值賦給範圍較窄的型別。我們來看看numeric_cast如何


幫忙。


從較大的型別到較小型別的賦值


從較大的型別(例如long)向較小的型別(例如short)賦值,有可能數值過大或過小而不能被目標型別所表示。


如果這發生了,結果是(是的,正如你猜到的)實現所定義的。我們稍後將討論無符號型別的潛在問題;我們


先從有符號型別開始。C++中有四個內建的有符號型別:
signed char
short int (short)
int
long int (long)


沒有人可以絕對肯定哪個型別比其它的大,但典型地,上面的列表是按大小遞增的,除了 int 和 long 通常


具有相同的值範圍。但它們都是獨立的型別,即使是有相同的大小。想檢視你的系統上的型別大小,可以使


用 sizeof(T) 或 std::numeric_limits<T>::max() 和 std::numeric_limits<T>::min().


當把一個有符號整數型別賦給另一個時,C++標準說:


"若目標型別為有符號型別,在數值可以被目標型別表示時,值不改變;否則,值為實現定義。"


以下程式碼段示範了看起來象是正確的賦值是如何導致實現定義的數值,最後看看如何通過numeric_cast的幫


助避免它們。
#include <iostream>
#include "boost/cast.hpp"
#include "boost/limits.hpp"


int main() {
  std::cout << "larger_to_smaller example\n";


  // 沒有使用numeric_cast的轉換
  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的轉換
  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 再次被輸出。你可能要問輸出的值是什麼?好的,因為賦值的結果屬於實現定義的行為,這取決


於你使用的平臺。在我的系統中,使用我的編譯器,它變成了一個大的負值,即它被迴繞了。必須執行前面


的程式碼才知道在你的系統中會有什麼結果。接著,再次執行相同的操作,但這次用了 numeric_cast. 第一個


轉型成功了,因為數值在範圍之內。而第二個轉型卻會失敗,結果是丟擲一個 bad_numeric_cast 異常。程


序的輸出如下。


larger_to_smaller example
s is: 32767
s is: -32768


s is: 32767
bad numeric cast: loss of range in numeric_cast




比避開實現定義行為更為重要的是,numeric_cast 幫助我們避免了錯誤,否則會很難捕捉到這些錯誤。那個


奇怪的數值可能被傳送到應用程式的其它部分,程式可能會繼續工作,但幾乎可以肯定將產生錯誤的結果。


當然,這僅對於特定的數值會發生這樣的情況,如果這些數值很少出現,那麼錯誤將很難被發現。這種錯誤


非常陰險,因為它們僅僅對某些特定值會發生,而不是總會發生。


精寬或取值範圍的損失並不常見,如果你不確定一個值對於目標型別是否過大或過小,numeric_cast 就是你


可以使用的工具。你甚至可以在不需要的時候使用 numeric_cast ;維護的程式設計師可能沒有象你一樣的洞察


力。注意,雖然我們在這裡只討論了有符號型別,但同樣的原理可應用於於無符號型別。


特殊情況:目標型別為無符號整數


無符號整數型別有一個非常有趣的特性,任何數值都有可以合法地賦給它們!對於無符號型別而言,無所謂


正或負的溢位。數值被簡單地對目標型別最大值加一取模。什麼意思?看看以下例子會更清楚一些。
#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可以表示的數值賦給它,然後再計算得到同樣的數值。賦值的動作可


以用這一行程式碼來示範:
long reduced=l%(std::numeric_limits<unsigned char>::max()+1);
複製程式碼
這種行為通常被稱為數值迴繞(value wrapping)。如果你想用這個特性,就沒有必要在這種情況下使用 


numeric_cast。此外,numeric_cast 也不接受它。numeric_cast的意圖是捕捉錯誤,而錯誤應該是因為使用者


的誤解而引起的。如果目標型別不能表示賦給它的數值,就丟擲一個 bad_numeric_cast 異常。因為無符號


整數的演算法是明確定義的,不會引起程式設計師的重大錯誤。對於 numeric_cast, 重要的是確保獲得實際的數值





有符號和無符號整數型別的混用


混用有符號和無符號型別可能很有趣,特別是執行算術操作時。普通的賦值也會產生微妙的問題。最常見的


問題是將一個負值賦給無符號型別。結果幾乎可以肯定不是你原來的意圖。另一種情形是從無符號型別到同


樣大小的有稱號型別的賦值。不知什麼原因,人們總是會很容易忘記無符號型別可以持有比同樣大小的有符


號型別更大的值。特別是在表示式或函式呼叫中更容易忘記。以下例子示範瞭如何通過numeric_cast來捕捉


這種常見的錯誤。
#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";
  }
}
複製程式碼
輸出清晰地表明瞭預期的錯誤。
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


基本的規則很簡單:無論何時在不同的型別間執行型別轉換,都應該使用 numeric_cast來保證轉換的安全。


浮點數型別


numeric_cast 不能幫助我們在浮點數間的轉換中避免精度的損失。原因是float, double, 和 long double


間的轉換不象整數型別間的隱式轉換那樣敏感。記住這點很重要,因為你可能會認為以下程式碼應該丟擲異常



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();
}
複製程式碼
執行這段程式碼不會有異常丟擲。在許多實現中,從 double 到 float 的轉換都會導致精度的損失,雖然C++


標準沒有保證會這樣。我們所能知道的就是,double 至少具有 float 的精度。


從浮點數型別轉為整數型別又會怎樣呢?當一個浮點數型別被轉換為一個整數型別,它會被截斷;小數部分


會被扔掉。numeric_cast 對截斷後的數值與目標型別進行相同的檢查,就象在兩個整數型別間的檢查一樣。
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();
}
複製程式碼
象前面的程式碼那樣進行範圍檢查以確保有效的賦值是一件令人畏縮的工作。雖然規則看起來很簡單,但是有


很多組合要被考慮。例如,測試從浮點數到整數的程式碼看起來就象這樣:
template <typename INT, typename FLOAT>
  bool is_valid_assignment(FLOAT f) {
    return std::numeric_limits<INT>::max() >=
      static_cast<INT>(f);
  }
複製程式碼
儘管我已經提起過在一個浮點數型別被轉換時,小數部分會被丟棄,在這個實現中還得很容易忽略這個錯誤


。這對於算術型別的轉換和提升是自然的。去掉 static_cast 就可以正確地測試,因為這樣 


numeric_limits<INT>::max 的結果會被轉換為浮點數型別。如果是浮點數型別轉為整數型別,它會被截斷;


換句話說,這個函式的問題在於丟失了小數部分。


總結


numeric_cast 提供了算術型別間高效的範圍檢查轉換。在目標型別可以持有所有源型別的值時,使用 


numeric_cast沒有額外的效率代價。它只在目標型別僅能表示源型別的值的子集時有影響。當轉換失敗時,


numeric_cast 通過丟擲一個 bad_numeric_cast異常來表示失敗。對於數值型別間的轉換有很多複雜的規則


,確保轉換的正確性是很重要的。


以下情況時使用 numeric_cast:
在無符號與有符號型別間進行賦值或比較時
在不同大小的整數型別間進行賦值或比較時
從一個函式返回型別向一個數值變數賦值,為了預防該函式未來的變化


在這裡注意到一個模式了嗎?模仿已有的語言和庫的名字及行為是簡化學習及使用的好方法,但也需要仔細


地考慮。增加內建的C++轉型就象沿著狹窄的小路行走;一旦迷路會帶來很高的代價。遵循語言的語法及語義


規則才是負責任的。事實上,對於初學者,內建的轉型操作符與看起來象轉型操作符的函式可能並沒有不同


,所以如果行為錯誤將會導致災難。numeric_cast 有著與 static_cast, dynamic_cast, 和 


reinterpret_cast類似的語法和語義。如果它看起來和用起來象轉型操作,它就是轉型操作,是對轉型操作


的一個良好的擴充套件。




lexical_cast


標頭檔案: "boost/lexical_cast.hpp"


所有應用都會使用字面轉換。我們把字串轉為數值,反之亦然。許多使用者定義的型別可以轉換為字串或


者由字串轉換而來。你常常是在需要這些轉換時才編寫程式碼,而更好的方法是提供一個可重用的實現。這


就是 lexical_cast的用途所在。你可以把lexical_cast 想象為使用一個 std::stringstream 作為字串與


數值的表示之間的翻譯器。這意味著它可以與任何用operator<<進行輸出的源以及任何用operator<<進行輸


入的目標一起工作。這個要求對於所有內建型別與多數使用者自定義型別(UDTs)都可以做到。


用法


lexical_cast 在型別之間進行轉換,就象其它的型別轉換操作一樣。當然,使它得以工作的必須是一個轉換


函式,但從概念上說,你可以把它視為轉型操作符。比起呼叫一堆的轉換子程式,或者是編寫自己的轉換代


碼,lexical_cast 可以更好地為任何滿足它的要求的型別服務。它的要求就是,源型別必須是可流輸出的


(OutputStreamable),而目標型別必須是可流輸入的 (InputStreamable)。另外,兩種型別都必須是可複製


構造的(CopyConstructible),並且目標型別還要是可預設構造的 (DefaultConstructible)和可賦值的


(Assignable)。可流輸出(OutputStreamable)意味著存在一個為該型別定義的operator<<,可流輸入


(InputStreamable)則要求有一個operator>>. 對於許多型別,包括所有內建型別和標準庫中的字串型別,


這個條件都滿足。要使用 lexical_cast, 就要包含標頭檔案 "boost/lexical_cast.hpp".


讓 lexical_cast 工作


我不想通過跟你示範手工編寫轉換用的程式碼來說明 lexical_cast 如何節省了你的時間,因為我可以很肯定


你一定寫過這樣的轉換程式碼,並且很可能不只一次。相反,只用一個例子來示範如何使用 lexical_cast 來


進行通用的(字面上的)型別轉換。
#include <iostream>
#include <string>
#include "boost/lexical_cast.hpp"


int main() {
  // string to int
  std::string s="42";
  int i=boost::lexical_cast<int>(s);


  // float to string
  float f=3.14151;
  s=boost::lexical_cast<std::string>(f);


  // literal to double
  double d=boost::lexical_cast<double>("2.52");


  // 失敗的轉換
  s="Not an int";
  try {
    i=boost::lexical_cast<int>(s);
  }
  catch(boost::bad_lexical_cast& e) {
    // 以上lexical_cast將會失敗,我們將進入這裡
  }
}
複製程式碼
這個例子僅僅示範了多種字面轉換情形中的幾種,我想你應該同意為了完成這些工作,通常你需要更多的代


碼。無論何時你不確定轉換是否有效,都應該用一個try/catch 塊來保護 lexical_cast ,就象你在這個例


子看到的那樣。你可能注意到了沒有辦法控制這些轉換的格式;如果你需要這種級別的控制,你要用 


std::stringstream!
如果你曾經手工進行過型別間的轉換,你應該知道,對於不同的型別,需要使用不同的辦法來處理轉換以及


可能出現的轉換失敗。這不僅是有點不便而已,它還妨礙了用泛型程式碼執行轉換的努力。稍後我們將看到 


lexical_cast 如何幫助你實現這一點。


這個例子中的轉換用手工來實現也非常簡單,但可能會失去轉型操作的美觀和優雅。而 lexical_cast 做起


來更簡單,並且更美觀。再考慮一下lexical_cast對與之一起工作的型別所需的簡單要求。考慮到對所有符


合該要求的型別的轉換可以在一行程式碼內完成的事實。再結合該實現依賴於標準庫的stringstream這一事實


,你可以看到 lexical_cast 不僅是執行字面轉換的便利方法,它更是C++編譯藝術的一個示範。


用 lexical_cast 進行泛型程式設計


作為使用lexical_cast進行泛型程式設計的簡單例子,來看一下如何用它建立一個 to_string 函式。這個函式接


受任何型別的引數(當然它要符合要求)並返回一個表示該值的 string 。標準庫的用法當然也可以在


std::stringstream的幫助下用幾行程式碼完成這個任務。在這裡,我們使用lexical_cast 來實現,只需要一


個前轉換函式呼叫及一些錯誤處理。
#include <iostream>
#include <string>
#include "boost/lexical_cast.hpp"


template <typename T> std::string to_string(const T& arg) {
  try {
    return boost::lexical_cast<std::string>(arg);
  }
  catch(boost::bad_lexical_cast& e) {
    return "";
  }
}


int main() {
  std::string s=to_string(412);
  s=to_string(2.357);
}
複製程式碼
這個小程式不僅易於實現,它還因為lexical_cast而增加了價值。


使類可以用於 lexical_cast


因為 lexical_cast 僅要求它所操作的型別提供適當的 operator<< 和 operator>> ,所以很容易為使用者自


定義型別增加字面轉換的支援。一個可以同時作為lexical_cast的目標和源的簡單UDT看起來就象這樣:
class lexical_castable {
public:
  lexical_castable() {};
  lexical_castable(const std::string s) : s_(s) {};


  friend std::ostream operator<<
    (std::ostream& o, const lexical_castable& le);
  friend std::istream operator>>
    (std::istream& i, lexical_castable& le);


private:
  virtual void print_(std::ostream& o) const {
    o << s_ <<"\n";
  }


  virtual void read_(std::istream& i) const {
    i >> s_;
  }


  std::string s_;
};


std::ostream operator<<(std::ostream& o,
  const lexical_castable& le) {
  le.print_(o);
  return o;
}


std::istream operator>>(std::istream& i, lexical_castable& le) {
  le.read_(i);
  return i;
}
複製程式碼
lexical_castable 類現在可以這樣用了:
int main(int argc, char* argv[]) {
  lexical_castable le;
  std::cin >> le;


  try {
    int i = boost::lexical_cast<int>(le);
  }
  catch(boost::bad_lexical_cast&) {
     std::cout << "You were supposed to enter a number!\n";
  }
}
複製程式碼
當然,輸入和輸出操作符最好可以允許這個類于于其它流。如果你使用標準庫的IOStreams,或者其它使用 


operator<< 和 operator>>的庫,你可能已經有很多可以用於 lexical_cast 的類。它們不需要進行修改。


直接


對它們進行字面轉換就行了!


總結


lexical_cast 是用於字串與其它型別之間的字面轉換的一個可重用及高效的工具。它是功能性和優雅性的


結合,是傑出程式設計師的偉大傑作。 不要在需要時實現小的轉換函式,更不要在其它函式中直接插入相關邏輯


,應該使用象 lexical_cast 這樣的泛型工具。它有助於使程式碼更清晰,並讓程式設計師專注於解決手上的問題





以下情況時使用lexical_cast:
從字串型別到數值型別的轉換
從數值型別到字串型別的轉換
你的自定義型別所支援的所有字面轉換






Conversion 總結


在這一部分Boost庫的講解中,我們一起學習了 Boost.Conversion 庫,從 polymorphic_cast開始。


polymorphic_cast 的基本原理是程式碼的清晰性和安全性,它使我們在程式碼中更靈活地表達我們的意圖,還有


安全性,與它的競爭者 dynamic_cast<T*>相比它更為安全,因為對結果指標的測試很容易忘記。


接著,你看到了安全的優化,使用 polymorphic_downcast, 它在除錯模式下增加了類似於dynamic_cast的安


全性,但卻是使用 static_cast 來進行轉換。這樣比單獨使用 static_cast 更安全。


numeric_cast 幫助你避免數值轉換中的某些困難。還有,程式碼的清晰性也得到提高,從而避免了未定義的行


為以及實現定義的行為。


最後一個是 lexical_cast. 沒有重複的轉換函式。這就是為什麼它被提議納入下一個版本的C++標準庫的原


因。它是一個非常小巧的、用於轉換不同的可流資料型別的工具。


如果你曾經看到過這些轉型的實現,你會同意它們之間沒有一個是複雜的。還有,它具有它們所需的洞察力


、遠見和知識,並正確地、可移植地、高效地實現了它們。不是所有人都認識到使用dynamic_cast時會發生


某些錯誤。不是很多人都知道整數型別轉換和提升的複雜規則。Boost提供的轉換操作包含了所有這些知識,


並具有良好的設計和測試;它們是你所要的最好的選擇。