2011年12月11日 星期日

C++: static_cast, const_cast, reinterpret_cast, dynamic_cast

 作者  cppOrz (cppOrz)                                        看板  C_and_CPP
 標題  Re: [問題] static.const.reinterpret_cast和c-sty …
 時間  Sun Mar 12 23:26:23 2006
───────────────────────────────────────


Sorry, 有很多地方錯誤,不得不指出來。
熱心回答問題是好的,但是最好對內容有一定的把握,以免誤導。

這一篇介紹 C++ 的四個關鍵字: static_cast, const_cast, reinterpret_cast,
以及 dynamic_cast 的意義用法。

學習任何編程語言,只單純學習語法是不夠的,要適當的運用某種語言機制,最好
要先知道該機制存在的目的,和什麼狀況下會需要用到它,否則還不如不要使用。


※ 引述《godfat (godfat 真常)》之銘言:
: 靜態轉型,簡單地說就是強制轉型,
: 只是不合理的轉型 compiler 會告訴你不能這樣轉
: 例如兩個毫無關聯的物件
: class A{};
: class B{};
: A a;
: static_cast<B>(a); // error

static_cast 用於「相關型別」之間的轉換,其目的是讓編譯器知道,
程序員是「有意識」的進行轉換動作,並非不小心打錯。例如:

float f = 123.456f;

int i = f; // 把 float 轉為 int,編譯器一般會給警告
int v = static_cast<int>(f); // 用 static_cast 通知編譯器,設計意途確實如此


此外 static_cast 也可用於繼承體系內的 downcast,例如:

struct B { void f(); };
struct D : B { void g(); } d;

B &b = d;
...

b.f(); // ok
b.g(); // 錯誤,b 是 reference to B 物件

static_cast<D&>(b).g(); // ok, 因為程序員知道 b 確實是參照某個 D 物件


: : const_cast
: 單純去掉物件的常數性 和/或 volatile 性

這裏是對的。

: int const i = 10;
: i = 5; // error
: const_cast<int&>(i) = 5; // ok
: (這邊我不太確定是不是這樣寫,很少用)
: (總之觀念是這樣)

舉例有誤。

const_cast 只是把常數性去掉,目的是方便設計,但是企圖通過 const_cast
修改一個 const 物件,其結果未定義(視實作環境是否對該 const 物件採取
保護措施)。

至於 const_cast 使用的時機,通常是為了在不修改既有模組(例如沒有 source
code 的程式庫)的情況下,解決兩個模組之間 const-correctness 不相容的問題
(通常是不能改的那個模組,其設計不夠周延)。例如:

struct C { void f(); }; // 假設 C 是一個既存的模組(不能修改)

void foo(C const &c) // foo 是自行設計的模組,c 物件只作為輸入,不會被更動
{
  ...

  c.f(); // 由於 c 是 referece to const C 物件,但 C::f 並未被設計為
         // const,因此這樣寫是不成立的。(編譯器會給錯誤或至少警告)

  const_cast<C&>(c).f(); // ok, 用 const_cast 去掉 c 的常數性
}

: : reinterpret_cast
: 強迫 compiler 把輸入型別視為欲轉換的型別
: 這個不太好解釋…總之就是暴力轉型就對了 XD
: 直接把該記憶體位置的資料視為欲轉換的型別來看待

reinterpret_cast 幾乎等於暴力轉型(指 C-style 轉型),但不能去掉物件
的常數性(也就是還不夠暴力)。它用來處理任意型別之間的轉換,通常是為
了爭取運算或儲存空間的效率時,所採取的低階操作。例如:

int IP_Addr = 0;
char *p = reinterpret_cast<char*>(&IP_Addr); // 轉型為 char *,以便於
                                             // 以 Byte 為單位來處理
p[0] = 192;
p[1] = 168;
p[2] = 0;
p[3] = 1;

傳統的 C-style 轉型相當於 static_cast, const_cast, reinterpret_cast
三合一(也就是暴力加三級)。


: 你漏了 dynamic_cast<>
: 這跟 static_cast<> 有些類似

dynamic_cast 和 static_cast 無關。

: dynamic_cast<> 只能對於具有多形性型別轉型,
: 也就是他至少得要有一個 virtual function

這裏是對的。dynamic_cast 是 RTTI 的一部份,它用來檢測一個多型基底
類別的 pointer 或 reference 所參照物件的實際型別。例如:

struct B1 { virtual void f() = 0; };
struct D1 { void f(); };
struct X : D1
{
  void f();
  void g();
};

void foo(B1 *b1) // foo 模組並不知道 b1 所參照物件的實際型別
{
  if (dynamic_cast<D1*>(b1)) // 如果 b1 參照的是 D1 物件
  {
    b1->f();
  }
  else if (D1 *d1 = dynamic_cast<X*>(b1)) // 如果 b1 參照的是 X 物件
  {
    d1->g();
  }
  else
  {
    ...
  }
}

此例中,dynamic_cast 的用法也是所謂的 downcast,但和 static_cast
不同的是,前者用於偵測未知多型物件的實際型別,而後者用於對已知物件
(可以是多型物件或普通物件)的轉型。

可以看出,B1, D1, X 的繼承體系設計並不是很理想,因為在這種情況下,應該
直接利用 virtual function 的動態多型機制,而非依賴 dynamic_cast 偵測物
件的型別。例如:

struct B2
{
  virtual void f() = 0;
  virtual void g() = 0;
};

struct D2 : B2
{
  void f();
  void g();
} d2;

struct Y : D2
{
  void f();
  void g();
} y;

void foo()
{
  B2 *b2 = &d2; // b2 參照 D2 物件
  b2->g(); // 實際上會執行 D2::g,不必依賴 dynamic_cast

  b2 = &y; // 換一下,改參照 Y 物件
  b2->g(); // 實際上會執行 Y::g
}

顯然,B2, D2, Y 的設計比較合理。事實上,如果所有的模組都是自行設計,
dynamic_cast 是多餘的。但它之所以存在,其目的就是為了解決「既存模組
」不能修改的問題。(最常見的例子,就是缺乏源代碼的程式庫)

前面的例子中,如果 B1, D1 兩模組不能修改,為了新擴充 X 模組,又要和
舊模組相容,dynamic_cast 在此情況下就能派上用場(反過來說,如果沒有
dynamic_cast,問題就會變得很麻煩,而且各種替代方案都不太安全);但
如果完全是可自行控制的設計,就應該儘量遵循 B2, D2, Y 的方式來組織。


以上是 dynamic_cast 的功能中,downcast 的部份。另外,由於 C++ 支援
多重繼承的機制,dynamic_cast 亦可用於 crosscast(橫向轉型),例如:

struct Z : B1, B2 // 多重繼承
{
  void f();
  void g();
} z;

void foo()
{
  B1 *b1 = &z;
  B2 *b2 = dynamic_cast<B2*>(b1); // crosscast
}

這個例子可以用來說明,dynamic_cast 實際上的動作是「型別檢測」,而非
僅僅是「轉型」,後者只是它輸出的結果。此例中,b1 表面上是 (B1 *),但
它實際上參照的是 Z 物件,因此 dynamic_cast 偵測的結果,就是它可以順
利轉為 (B2 *) 。


: struct Base{virtual ~Base(){}};
: struct Derived: public Base{};
: Base pb = new Derived;
: Derived pd = dynamic_cast<Derived*>(pb);
: 如果 pb 真的是指向 Derived, 則 dynamic_cast<> 傳回 pb 的地址
: 否的話,pd 為 NULL
: boost 有提供 polymorphic_cast<>, 用處同 dynamic_cast<>
: 差別在於錯誤時不是傳回 NULL, 而是丟出 std::bad_cast
: polymorphic_downcast<> 則是專門用來在已知必然成功的 downcast
: 這種時候內建的 static_cast<> 和 dynamic_cast<> 其實都可以用,
: 只是 static_cast<> 沒有錯誤檢查,dynamic_cast<> 效率太差
: polymorphic_downcast<> 則使用 assert(); 來檢查是否成功
: http://www.boost.org/libs/conversion/cast.htm
: : c-style cast
: 等同於上面全部...
: 在 C++ 中替他們分類,避免造成混淆
: 不過對初學者來說的話這麼多才是混淆吧,我猜 :p
(下略)

--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 59.120.214.120
推 godfat:感謝指導,雖然我覺得跟我講的沒啥衝突 XD 大概是太隨便了   03/13 01:44
※ 編輯: cppOrz          來自: 59.120.214.120       (03/13 04:50)
※ crazying:轉錄至看板 NTUGIEE_EDA                                 03/13 11:58
推 abovelight:推一個~了解更深入了                                  03/14 18:16

沒有留言:

張貼留言