青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

洛譯小筑

別來無恙,我的老友…
隨筆 - 45, 文章 - 0, 評論 - 172, 引用 - 0
數據加載中……

[ECPP讀書筆記 條目27] 盡量少用轉型操作

C++的設計初衷之一就是:確保代碼遠離類型錯誤。從理論上來講,如果你的程序順利通過了編譯,那么它就不會對任何對象嘗試去做任何不安全或無意義的操作。這是一項非常有價值的保證。你不應該輕易放棄它。

然而遺憾的是,轉型擾亂了原本井然有序的類型系統。它可以帶來無窮無盡的問題,一些是顯而易見的,但另一些則是極難察覺的。如果你是一名從C、Java或者C#轉向C++的程序員的話,那么請注意了,因為相對C++而言,轉型在這些語言中更加重要,而且帶來的危險也小得多。但是C++不是C,也不是Java、C#。在C++中,轉型是需要你格外注意的議題。

讓我們從復習轉型的語法開始,因為實現轉型有三種不同但是等價的方式。C風格的轉型是這樣的:

(T) 表達式                          // 表達式轉型為T類型的

函數風格轉型的語法如下:

T(表達式)                           //表達式轉型為T類型的

兩者之間在含義上沒有任何的區別。這僅僅是你把括號放在哪兒的問題。我把這兩種形式稱為“懷舊風格的轉型”。

C++還提供了四種新的轉型的形式(通常稱為“現代風格”或“C++風格”的轉型):

const_cast<T>(表達式)

dynamic_cast<T>(表達式)

reinterpret_cast<T>(表達式)

static_cast<T>(表達式)

四者各司其職:

const_cast通常用來脫去對象的恒定性。C++風格轉型中只有它能做到這一點。

dynamic_cast主要用于進行“安全的向下轉型”,也就是說,它可以決定一個對象的類型是否屬于某一個特定的類型繼承層次結構中。它是唯一一種懷舊風格語法所無法替代的轉型。它也是唯一一種可能會帶來顯著運行時開銷的轉型。(稍候會具體講解。)

reinterpret_cast是為底層轉型而特別設置的,這類轉型可能會依賴于實現方式,比如說,將一個指針轉型為一個int值。除了底層代碼以外,要格外注意避免這類轉型。此類轉型在這本書中我只用過一次,而也是在討論如何編寫一個針對未分配內存的調試分配器(參見條目50)用到。

static_cast可以用于強制隱式轉換(比如說,將一個非const的對象轉換為const對象(就像條目3中一樣),int轉換為double,等等)。它可以用于大多數這類轉換的逆操作(比如說,void*指針轉換為包含類型的指針,指向基類的指針轉換為指向繼承類的指針),但是它不能進行從const到非const對象的轉型。(只有const_cast可以。)

懷舊風格的轉型在C++中仍然是合法的,但是這里更推薦使用新形式。首先,它們在代碼中更加易于辨認(不僅對人,而且對grep這樣的工具也是如此),對于那些類型系統亂成一團的代碼,這樣做可以減少我們為類型頭疼的時間。其次,對每次轉型的目的更加細化,使得編譯器主動診斷用法錯誤成為可能。比如說,如果你嘗試通過轉型脫去恒定性的話,你只能使用const_cast,如果你嘗試使用其它現代風格的轉型,你的代碼就不會通過編譯。

需要使用懷舊風格轉型的唯一的一個地方就是:調用一個explicit的構造函數來為一個函數傳遞一個對象。比如:

class Widget {

public:

  explicit Widget(int size);

  ...

};

 

void doSomeWork(const Widget& w);

 

doSomeWork(Widget(15));                  // 函數風格轉型

                                         // 基于int創建一個Widget

 

doSomeWork(static_cast<Widget>(15));     // C++風格轉型

                                         // 基于int創建一個Widget

出于某些原因,手動創建一個對象“感覺上”并不類似于一次轉型,所以在這種情況下應更趨向于使用函數風格的轉型而不是static_cast。同時,由于即使在你寫下的代碼可能會導致核心轉儲時,你仍然會“感覺”你有充足的理由那樣做,因此你可能要忽略你的直覺,自始至終使用現代風格的轉型。

許多程序員相信轉型只是告訴編譯器將一個類型作為另一種來對待,僅此而已,殊不知任何種類的類型轉換(無論是顯式的還是通過編譯器隱式進行的)通常會在運行時引入一些新的需要執行的代碼。比如說,在下面的代碼片斷中:

int x, y;

...

double d = static_cast<double>(x)/y;      // x除以y,用浮點數保存商值

int xdouble的轉型幾乎一定要引入新的代碼,因為在絕大多數架構中,intdouble的底層表示模式是不同的。這并不那么令人吃驚,但是下面的示例也許會讓你的眼界更加開闊些:

class Base { ... };

 

class Derived: public Base { ... };

 

Derived d;

 

Base *pb = &d;                             // 隱式轉換: Derived* => base*

這里我們創建了一個基類的指針,并讓其指向了一個派生類的對象,但是某些時候,這兩個指針值并不會保持一致。如果真的這樣了,系統會在運行時為Derived*指針應用一個偏移值來取得正確的Base*指針的值。

上文的示例告訴我們:一個單獨的對象(比如一個Derived的對象)可能會擁有一個以上的地址(比如,一個Base*指針指向它的地址和一個Derived*指針指向它的地址)。這件事在C語言中是絕不會發生的。同樣在JavaC#中均不會發生。但在C++中的的確確發生了。實際上,在使用多重繼承時這件事幾乎是必然的,而在單繼承環境下也有可能發生。這意味著你應該避免去假設或推定C++放置對象的方式,同時你應該避免基于這樣的假設來進行轉型。比如,如果你將對象地址轉型為char*指針,然后再對其進行指針運算,通常都會使程序陷入未定義行為。

但是請注意,我說過“某些時候”才需要引入偏移值。對象放置的方法、地址計算的方法都是因編譯器而異的。這就意味著,僅僅由于你“知道對象如何放置”,你對在某一個平臺上轉型的做法可能充滿信心,但它在另一些平臺上卻是一錢不值。世界上有許多程序員為此付出了慘痛的代價。

關于轉型的一件有趣事情是:你很容易編寫一些 “看上去正確”的東西,但實際上它們是錯誤的。舉例說,許多應用程序框架需要在派生類中實現一個虛擬成員函數,并首先讓這些函數去調用基類中對應的函數。假設我們有一個Window基類和一個SpecialWindow派生類,兩者都定義了虛函數onResize。繼續假設:SpecialWindowonResize首先會調用WindowonResize。以下是實現方法,它乍看上去是正確的,其實不然:

class Window {                             // 基類

public:

  virtual void onResize() { ... }          // 基類onResize的實現

  ...

};

 

class SpecialWindow: public Window {      // 派生類

public:

  virtual void onResize() {               // 派生類onResize的實現

static_cast<Window>(*this).onResize();

                                       // *this轉型為Window

                                          // 然后調用它的onResize,

                                          // 這樣不會正常工作!

 

    ...                                   // 完成SpecialWindow獨有的任務

  }

  ...

};

上面代碼中的轉型操作已經用黑體字標出。(這是一個現代風格的轉型,但是如果使用懷舊風格也不會帶來任何改善。)就像你所預料的那樣,代碼將會將*this轉型為一個Window,因此這里的onResize的調用應該是Window::onResize。而你一定不會預料到,當前對象并沒有調用這一函數。取而代之的是,轉型過程創建了一個新的,*this中基類部分的一個臨時副本,然后調用這一副本的onResize。上面的代碼將不會調用當前對象的Window::onResize,然后進行對象中的具體到SpecialWindow的動作;而是再對當前對象進行SpecialWindow行為之前,去調用當前對象的基類部分的副本中的Window::onResize。如果Window::onResize希望修改當前對象(這也不是完全不可能,因為onResize是一個非const的成員函數),實際上當前對象不會受到任何影響。取而代之的是,這一對象的那個副本將會被修改。然而,如果SpecialWindow::onResize希望修改當前對象,當前對象將會被修改,這將導致下面的情景:代碼將會使當前對象處于病態中——它基類部分的修改沒有進行,而派生類部分的修改卻完成了。

解決方案就是:避免轉型。用你真正需要的操作加以替換。你并不希望欺騙編譯器將一個*this誤識別為一個基類對象;你希望對當前對象調用onResize的基類版本。那么就這樣編寫好了:

class SpecialWindow: public Window {

public:

  virtual void onResize() {

    Window::onResize();            // *this調用Window::onResize

    ...

  }

  ...

 

};

這個示例同時告訴我們:如果你發現你的工作需要進行轉型,那么此處就告訴了你當前的努力方向可能是錯誤的。尤其是在你期望使用dynamic_cast時。

在深入探究dynamic_cast的實現設計方式之前,有必要先了解一下:大多數dynamic_cast實現的運行速度是非常緩慢的。比如說,至少有一種普遍的實現是通過比較各個類名的字符串。如果你正在針對一個四層深的單一繼承層次結構中的一個對象進行dynamic_cast,那么這種實現方式下,每一次dynamic_cast都會占用四次調用strcmp的時間用于比較類名。顯然地,更深的或者使用多重繼承的層次結構的開銷將會更為顯著。一些實現以這種方式運行也是有它的根據的(它們這樣做是為了支持動態鏈接)。在對性能要求較高的代碼中,要在整體上時刻對轉型持謹慎的態度,你應該特別謹慎地使用dynamic_cast

一般說來,如果你確信一些對象是派生類的,那么當你對這些對象進行派生類的操作時,你只有一個指針或者一個指向基類的引用能操作這類對象,dynamic_cast將可能派上用場。一般有兩條途徑來避免這一問題。

首先,可以使用容器來保存直接指向派生類對象的指針(通常是智能指針,參見條目13),這樣就無需通過基類接口來操作這些對象。比如說,在我們的Window/SpecialWindow層次結構中,如果只有SpecialWindow支持閃爍效果,我們也許可以這樣做:

class Window { ... };

 

class SpecialWindow: public Window {

public:

  void blink();

  ...

};

 

typedef std::vector<std::tr1::shared_ptr<Window> > VPW;

// 關于tr1::shared_ptr的信息請參見條目13

 

VPW winPtrs;

 

...

 

for (VPW::iterator iter = winPtrs.begin();    // 不好的代碼。

     iter != winPtrs.end();                   // 使用dynamic_cast

     ++iter) {

  if (SpecialWindow *psw = dynamic_cast<SpecialWindow*>(iter->get()))

     psw->blink();

}

這里有更好的解決方案:

typedef std::vector<std::tr1::shared_ptr<SpecialWindow> > VPSW;

 

VPSW winPtrs;

 

...

 

for (VPSW::iterator iter = winPtrs.begin();   // 更好的代碼

     iter != winPtrs.end();                   // 無需dynamic_cast

     ++iter)

  (*iter)->blink();

當然,在使用這一方案時,在同一容器中放置的Window 派生對象的類型是受到限制的。為了使用更多的Window類型,你可能需要多個類型安全的容器。

一個可行的替代方案是:在基類中提供虛函數,然后按需配置。這樣對于所有可能的Window派生類型,你都可以通過基類接口來進行操作了。比如說,盡管只有SpecialWindow可以閃爍,但是在基類中聲明這一函數也是有意義的,可以提供一個默認的實現,但不去做任何事情:

class Window {

public:

  virtual void blink() {}          // 默認實現不做任何事情;

  ...                              // 條目34將介紹:

};                                 // 提供默認實現可能是個壞主意

 

class SpecialWindow: public Window {

public:

  virtual void blink() { ... };    // 在這一類型中

  ...                              // blink函數會做一些事情

};

 

typedef std::vector<std::tr1::shared_ptr<Window> > VPW;

 

VPW winPtrs;                       // 容器中保存著(指向)

...                                // 所有可能的Window類型

 

for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)

                                   // 請注意這里沒有dynamic_cast

  (*iter)->blink();

上面的這兩種實現(使用類型安全的容器,或者在層次結構的頂端添加虛函數)都不是萬能的。但在大多數情況下,它們是dynamic_cast良好的替代方案。如果你發現其中一種方案可行,大可以欣然接受。

關于dynamic_cast有一件事情自始至終都要注意,那就是:避免級聯式使用。就是說要避免類似下面的代碼出現:

class Window { ... };

...                                // 此處定義派生類

typedef std::vector<std::tr1::shared_ptr<Window> > VPW;

VPW winPtrs;

...

for (VPW::iterator iter = winPtrs.begin(); iter != winPtrs.end(); ++iter)

{

  if (SpecialWindow1 *psw1 =

       dynamic_cast<SpecialWindow1*>(iter->get())) { ... }

  else if (SpecialWindow2 *psw2 =

            dynamic_cast<SpecialWindow2*>(iter->get())) { ... }

  else if (SpecialWindow3 *psw3 =

            dynamic_cast<SpecialWindow3*>(iter->get())) { ... }

  ...

}

這樣的代碼經編譯后得到的可執行代碼將是冗長而性能低下的,并且十分脆弱,這是因為每當Window的類層次結構有所改變時,就需要檢查所有這樣的代碼,以斷定它們是否需要更新。(比如說,如果添加了一個新的派生類,上面的級聯操作中就需要添加一個新的條件判斷分支。)這樣的代碼還是由虛函數調用的方式取代為好。

優秀的C++代碼中使用轉型應該是十分謹慎的,但是一般說來并不是要全盤否定。比如說,前文中的doSomeWork(Widget(15))
中從intdouble的轉型,就是一次合理而有用的轉型,盡管它并不是必需的。(代碼可以這樣重寫:聲明一個新的double類型的變量,并且用x的值對其進行初始化。)和其他絕大多數可以結構一樣,轉型應該盡可能的與其它代碼隔離,典型的方法是將其隱藏在函數中,這些函數的的接口就可以防止調用者接觸其內部復雜的操作。

時刻牢記

盡可能避免使用轉型,尤其是在對性能敏感的代碼中不要使用動態轉型dynamic_cast。如果一個設計方案需要使用轉型,要嘗試尋求一條不需要轉型的方案來取代。

在必須使用轉型時,要嘗試將其隱藏在一個函數中。這樣客戶就可以調用這些函數,而不是在他們自己的代碼中使用轉型。

要多用C++風格的轉型,少用懷舊風格的轉型?,F代的轉型更易讀,而且功能更為具體化。

posted on 2007-09-13 22:27 ★ROY★ 閱讀(1367) 評論(3)  編輯 收藏 引用 所屬分類: Effective C++

評論

# re: 【讀書筆記】[Effective C++第3版][第27條]盡量不要使用類型轉換  回復  更多評論   

寫得不錯!

# re: 【讀書筆記】[Effective C++第3版][第27條]盡量不要使用類型轉換  回復  更多評論   

"轉型"這一詞是首創嗎?“強制類型轉換”太長了點,“轉型”是個好詞。
2007-09-27 09:11 | 金慶

# re: 【讀書筆記】[Effective C++第3版][第27條]盡量不要使用類型轉換  回復  更多評論   

轉型是慣用詞匯。我只是拿來而已。合合。
2007-09-27 17:59 | tiandejian
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美a级在线| 久久婷婷麻豆| 国产色综合网| 欧美日韩精品在线视频| 欧美电影打屁股sp| 欧美精品乱码久久久久久按摩| 久久综合精品国产一区二区三区| 久久久999精品| 免费在线成人av| 欧美韩国日本综合| 欧美视频在线观看免费网址| 欧美日韩久久久久久| 国产精品日韩欧美一区二区三区| 国产一区二区三区av电影| 一色屋精品视频免费看| 亚洲精品日韩欧美| 午夜精品福利一区二区三区av | 亚洲国产电影| 一区二区三区免费在线观看| 午夜亚洲精品| 欧美v日韩v国产v| 一本高清dvd不卡在线观看| 亚洲欧美变态国产另类| 久久久精品2019中文字幕神马| 欧美国产一区二区| 国产一区高清视频| 亚洲深夜福利网站| 美脚丝袜一区二区三区在线观看 | 欧美精品首页| 国产农村妇女精品| 亚洲精品乱码久久久久久黑人| 午夜亚洲激情| 91久久精品国产91性色| 亚洲综合国产| 欧美日韩中文字幕在线视频| 激情综合中文娱乐网| 亚洲尤物影院| 欧美福利影院| 久久大逼视频| 国产精品免费一区二区三区观看| 亚洲激情小视频| 久久亚洲二区| 国产精品www| 国产精品视区| 在线中文字幕一区| 欧美激情影院| 久久精品国产亚洲aⅴ| 欧美三级网页| av成人福利| 美女国内精品自产拍在线播放| 亚洲在线电影| 国产精品青草综合久久久久99 | 亚洲一区欧美| 欧美日韩一区二区在线播放| 亚洲精品中文字幕在线观看| 欧美国产第一页| 巨乳诱惑日韩免费av| 黄色资源网久久资源365| 久久久爽爽爽美女图片| 欧美一区2区三区4区公司二百| 国产精品一区久久久| 欧美一区二区三区四区在线 | 亚洲一级在线| 国产精品裸体一区二区三区| 亚洲一区制服诱惑| 亚洲一区二区三区免费视频| 欧美性感一类影片在线播放| 亚洲欧美国产一区二区三区| 一本色道久久| 国产精品免费电影| 久久经典综合| 久久一区二区三区av| 亚洲人久久久| 日韩一级裸体免费视频| 国产精品观看| 久久精品中文| 麻豆久久久9性大片| 夜夜精品视频一区二区| 亚洲自拍偷拍网址| 伊人久久成人| 亚洲精品久久久蜜桃| 国产精品99免费看| 欧美专区第一页| 欧美福利网址| 久久国产毛片| 久久一区亚洲| 亚洲综合精品| 久久综合网络一区二区| 中文一区二区| 久久精品在线| 亚洲特黄一级片| 久久国产毛片| 亚洲小说欧美另类社区| 久久国产一区二区| 一区二区三区欧美在线观看| 性18欧美另类| 亚洲最新在线视频| 欧美性久久久| 中文亚洲免费| 欧美亚洲日本一区| 亚洲国产乱码最新视频| 一区二区三区四区五区精品视频| 国产精品一区在线播放| 欧美激情国产日韩| 欧美四级剧情无删版影片| 久久综合色播五月| 欧美日韩亚洲91| 免费视频一区| 国产精品久久久久一区二区三区共| 免费一级欧美片在线观看| 国产精品乱人伦一区二区| 欧美激情一区二区三区在线视频观看| 国产精品国产三级欧美二区| 欧美大片在线观看一区| 国产乱码精品一区二区三区不卡| 欧美顶级大胆免费视频| 亚洲精品视频在线播放| 激情欧美国产欧美| 欧美亚洲在线播放| 亚洲伊人色欲综合网| 欧美成人在线免费观看| 久久久久国产精品厨房| 欧美天堂亚洲电影院在线观看| 欧美激情一区三区| 亚洲激情六月丁香| 久久久久久999| 久久久久国产免费免费| 国产精品免费在线| 亚洲毛片在线观看| 亚洲精品在线三区| 猛男gaygay欧美视频| 免费久久精品视频| 国模私拍一区二区三区| 欧美中在线观看| 久久久久久国产精品mv| 国产精品人人爽人人做我的可爱 | 亚洲国产精品激情在线观看| 怡红院精品视频在线观看极品| 小黄鸭精品密入口导航| 久久福利毛片| 国产一区视频网站| 久久九九有精品国产23| 蜜桃av综合| 亚洲电影免费观看高清完整版在线| 欧美一区二区三区喷汁尤物| 久久婷婷色综合| 韩国av一区二区三区| 美女图片一区二区| 亚洲精品欧美一区二区三区| 亚洲一区尤物| 国产乱人伦精品一区二区 | 欧美日韩免费观看一区二区三区| 亚洲精品日韩精品| 性欧美暴力猛交另类hd| 国产一区二区三区观看| 久久精品一区二区| 亚洲国产岛国毛片在线| 一区二区三区视频免费在线观看| 欧美日韩成人在线播放| 亚洲一区欧美一区| 美女91精品| 一本一本久久a久久精品综合麻豆| 久久久99国产精品免费| 欧美一区二区视频免费观看| 欧美制服丝袜| 精品99一区二区| 欧美日韩www| 欧美在线1区| 亚洲欧洲一区二区在线播放| 亚洲欧美日韩成人| 亚洲国产99| 国产精品尤物| 欧美99久久| 午夜免费电影一区在线观看| 欧美黄色影院| 欧美在线免费观看亚洲| 91久久国产精品91久久性色| 国产精品国产三级国产aⅴ浪潮 | 欧美久久久久久蜜桃| 亚洲一区二区在线播放| 亚洲承认在线| 欧美中文字幕在线观看| 亚洲精品女人| 国内精品国产成人| 国产精品成人免费视频| 久久综合色一综合色88| 亚洲欧美www| 亚洲美女性视频| 欧美成人免费小视频| 久久国产精品72免费观看| 在线视频日韩精品| 亚洲日本电影| 国内激情久久| 欧美天堂亚洲电影院在线播放| 老牛国产精品一区的观看方式| 午夜一区二区三区不卡视频| 一本大道久久a久久精二百| 欧美成人一二三| 久久亚洲国产精品日日av夜夜| 亚洲综合精品|