• <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>

            洛譯小筑

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

            [ECPP讀書筆記 條目27] 盡量少用轉(zhuǎn)型操作

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

            然而遺憾的是,轉(zhuǎn)型擾亂了原本井然有序的類型系統(tǒng)。它可以帶來無窮無盡的問題,一些是顯而易見的,但另一些則是極難察覺的。如果你是一名從C、Java或者C#轉(zhuǎn)向C++的程序員的話,那么請注意了,因?yàn)橄鄬++而言,轉(zhuǎn)型在這些語言中更加重要,而且?guī)淼奈kU(xiǎn)也小得多。但是C++不是C,也不是Java、C#。在C++中,轉(zhuǎn)型是需要你格外注意的議題。

            讓我們從復(fù)習(xí)轉(zhuǎn)型的語法開始,因?yàn)閷?shí)現(xiàn)轉(zhuǎn)型有三種不同但是等價(jià)的方式。C風(fēng)格的轉(zhuǎn)型是這樣的:

            (T) 表達(dá)式                          // 表達(dá)式轉(zhuǎn)型為T類型的

            函數(shù)風(fēng)格轉(zhuǎn)型的語法如下:

            T(表達(dá)式)                           //表達(dá)式轉(zhuǎn)型為T類型的

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

            C++還提供了四種新的轉(zhuǎn)型的形式(通常稱為“現(xiàn)代風(fēng)格”或“C++風(fēng)格”的轉(zhuǎn)型):

            const_cast<T>(表達(dá)式)

            dynamic_cast<T>(表達(dá)式)

            reinterpret_cast<T>(表達(dá)式)

            static_cast<T>(表達(dá)式)

            四者各司其職:

            const_cast通常用來脫去對象的恒定性。C++風(fēng)格轉(zhuǎn)型中只有它能做到這一點(diǎn)。

            dynamic_cast主要用于進(jìn)行“安全的向下轉(zhuǎn)型”,也就是說,它可以決定一個(gè)對象的類型是否屬于某一個(gè)特定的類型繼承層次結(jié)構(gòu)中。它是唯一一種懷舊風(fēng)格語法所無法替代的轉(zhuǎn)型。它也是唯一一種可能會(huì)帶來顯著運(yùn)行時(shí)開銷的轉(zhuǎn)型。(稍候會(huì)具體講解。)

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

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

            懷舊風(fēng)格的轉(zhuǎn)型在C++中仍然是合法的,但是這里更推薦使用新形式。首先,它們在代碼中更加易于辨認(rèn)(不僅對人,而且對grep這樣的工具也是如此),對于那些類型系統(tǒng)亂成一團(tuán)的代碼,這樣做可以減少我們?yōu)轭愋皖^疼的時(shí)間。其次,對每次轉(zhuǎn)型的目的更加細(xì)化,使得編譯器主動(dòng)診斷用法錯(cuò)誤成為可能。比如說,如果你嘗試通過轉(zhuǎn)型脫去恒定性的話,你只能使用const_cast,如果你嘗試使用其它現(xiàn)代風(fēng)格的轉(zhuǎn)型,你的代碼就不會(huì)通過編譯。

            需要使用懷舊風(fēng)格轉(zhuǎn)型的唯一的一個(gè)地方就是:調(diào)用一個(gè)explicit的構(gòu)造函數(shù)來為一個(gè)函數(shù)傳遞一個(gè)對象。比如:

            class Widget {

            public:

              explicit Widget(int size);

              ...

            };

             

            void doSomeWork(const Widget& w);

             

            doSomeWork(Widget(15));                  // 函數(shù)風(fēng)格轉(zhuǎn)型

                                                     // 基于int創(chuàng)建一個(gè)Widget

             

            doSomeWork(static_cast<Widget>(15));     // C++風(fēng)格轉(zhuǎn)型

                                                     // 基于int創(chuàng)建一個(gè)Widget

            出于某些原因,手動(dòng)創(chuàng)建一個(gè)對象“感覺上”并不類似于一次轉(zhuǎn)型,所以在這種情況下應(yīng)更趨向于使用函數(shù)風(fēng)格的轉(zhuǎn)型而不是static_cast。同時(shí),由于即使在你寫下的代碼可能會(huì)導(dǎo)致核心轉(zhuǎn)儲(chǔ)時(shí),你仍然會(huì)“感覺”你有充足的理由那樣做,因此你可能要忽略你的直覺,自始至終使用現(xiàn)代風(fēng)格的轉(zhuǎn)型。

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

            int x, y;

            ...

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

            int xdouble的轉(zhuǎn)型幾乎一定要引入新的代碼,因?yàn)樵诮^大多數(shù)架構(gòu)中,intdouble的底層表示模式是不同的。這并不那么令人吃驚,但是下面的示例也許會(huì)讓你的眼界更加開闊些:

            class Base { ... };

             

            class Derived: public Base { ... };

             

            Derived d;

             

            Base *pb = &d;                             // 隱式轉(zhuǎn)換: Derived* => base*

            這里我們創(chuàng)建了一個(gè)基類的指針,并讓其指向了一個(gè)派生類的對象,但是某些時(shí)候,這兩個(gè)指針值并不會(huì)保持一致。如果真的這樣了,系統(tǒng)會(huì)在運(yùn)行時(shí)為Derived*指針應(yīng)用一個(gè)偏移值來取得正確的Base*指針的值。

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

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

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

            class Window {                             // 基類

            public:

              virtual void onResize() { ... }          // 基類onResize的實(shí)現(xiàn)

              ...

            };

             

            class SpecialWindow: public Window {      // 派生類

            public:

              virtual void onResize() {               // 派生類onResize的實(shí)現(xiàn)

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

                                                   // *this轉(zhuǎn)型為Window,

                                                      // 然后調(diào)用它的onResize

                                                      // 這樣不會(huì)正常工作!

             

                ...                                   // 完成SpecialWindow獨(dú)有的任務(wù)

              }

              ...

            };

            上面代碼中的轉(zhuǎn)型操作已經(jīng)用黑體字標(biāo)出。(這是一個(gè)現(xiàn)代風(fēng)格的轉(zhuǎn)型,但是如果使用懷舊風(fēng)格也不會(huì)帶來任何改善。)就像你所預(yù)料的那樣,代碼將會(huì)將*this轉(zhuǎn)型為一個(gè)Window,因此這里的onResize的調(diào)用應(yīng)該是Window::onResize。而你一定不會(huì)預(yù)料到,當(dāng)前對象并沒有調(diào)用這一函數(shù)。取而代之的是,轉(zhuǎn)型過程創(chuàng)建了一個(gè)新的,*this中基類部分的一個(gè)臨時(shí)副本,然后調(diào)用這一副本的onResize。上面的代碼將不會(huì)調(diào)用當(dāng)前對象的Window::onResize,然后進(jìn)行對象中的具體到SpecialWindow的動(dòng)作;而是再對當(dāng)前對象進(jìn)行SpecialWindow行為之前,去調(diào)用當(dāng)前對象的基類部分的副本中的Window::onResize。如果Window::onResize希望修改當(dāng)前對象(這也不是完全不可能,因?yàn)?span style="font-family:"Courier New";">onResize是一個(gè)非const的成員函數(shù)),實(shí)際上當(dāng)前對象不會(huì)受到任何影響。取而代之的是,這一對象的那個(gè)副本將會(huì)被修改。然而,如果SpecialWindow::onResize希望修改當(dāng)前對象,當(dāng)前對象將會(huì)被修改,這將導(dǎo)致下面的情景:代碼將會(huì)使當(dāng)前對象處于病態(tài)中——它基類部分的修改沒有進(jìn)行,而派生類部分的修改卻完成了。

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

            class SpecialWindow: public Window {

            public:

              virtual void onResize() {

                Window::onResize();            // *this調(diào)用Window::onResize

                ...

              }

              ...

             

            };

            這個(gè)示例同時(shí)告訴我們:如果你發(fā)現(xiàn)你的工作需要進(jìn)行轉(zhuǎn)型,那么此處就告訴了你當(dāng)前的努力方向可能是錯(cuò)誤的。尤其是在你期望使用dynamic_cast時(shí)。

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

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

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

            class Window { ... };

             

            class SpecialWindow: public Window {

            public:

              void blink();

              ...

            };

             

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

            // 關(guān)于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();

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

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

            class Window {

            public:

              virtual void blink() {}          // 默認(rèn)實(shí)現(xiàn)不做任何事情;

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

            };                                 // 提供默認(rèn)實(shí)現(xiàn)可能是個(gè)壞主意

             

            class SpecialWindow: public Window {

            public:

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

              ...                              // blink函數(shù)會(huì)做一些事情

            };

             

            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();

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

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

            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())) { ... }

              ...

            }

            這樣的代碼經(jīng)編譯后得到的可執(zhí)行代碼將是冗長而性能低下的,并且十分脆弱,這是因?yàn)槊慨?dāng)Window的類層次結(jié)構(gòu)有所改變時(shí),就需要檢查所有這樣的代碼,以斷定它們是否需要更新。(比如說,如果添加了一個(gè)新的派生類,上面的級聯(lián)操作中就需要添加一個(gè)新的條件判斷分支。)這樣的代碼還是由虛函數(shù)調(diào)用的方式取代為好。

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

            時(shí)刻牢記

            盡可能避免使用轉(zhuǎn)型,尤其是在對性能敏感的代碼中不要使用動(dòng)態(tài)轉(zhuǎn)型dynamic_cast。如果一個(gè)設(shè)計(jì)方案需要使用轉(zhuǎn)型,要嘗試尋求一條不需要轉(zhuǎn)型的方案來取代。

            在必須使用轉(zhuǎn)型時(shí),要嘗試將其隱藏在一個(gè)函數(shù)中。這樣客戶就可以調(diào)用這些函數(shù),而不是在他們自己的代碼中使用轉(zhuǎn)型。

            要多用C++風(fēng)格的轉(zhuǎn)型,少用懷舊風(fēng)格的轉(zhuǎn)型?,F(xiàn)代的轉(zhuǎn)型更易讀,而且功能更為具體化。

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

            評論

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

            寫得不錯(cuò)!

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

            "轉(zhuǎn)型"這一詞是首創(chuàng)嗎?“強(qiáng)制類型轉(zhuǎn)換”太長了點(diǎn),“轉(zhuǎn)型”是個(gè)好詞。
            2007-09-27 09:11 | 金慶

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

            轉(zhuǎn)型是慣用詞匯。我只是拿來而已。合合。
            2007-09-27 17:59 | tiandejian
            亚洲中文字幕无码久久2017| 亚洲香蕉网久久综合影视| 欧美亚洲色综久久精品国产| 久久久精品人妻一区二区三区蜜桃 | 国产精品99久久久久久宅男 | 国产综合精品久久亚洲| 综合久久给合久久狠狠狠97色| 国产高潮国产高潮久久久| 精品久久国产一区二区三区香蕉| 久久久无码精品午夜| www.久久精品| 久久精品99久久香蕉国产色戒| 狠狠色综合久久久久尤物| 亚洲乱码精品久久久久..| 久久精品综合网| 久久精品国产一区二区| 99久久国产免费福利| 日韩亚洲欧美久久久www综合网| 国产午夜福利精品久久2021| 亚洲国产成人久久综合一区77| 久久96国产精品久久久| 91精品国产综合久久精品| 久久人人爽人爽人人爽av| a级成人毛片久久| 91精品国产乱码久久久久久| 久久综合88熟人妻| 99久久精品免费看国产一区二区三区 | 新狼窝色AV性久久久久久| 亚洲国产精品成人久久蜜臀 | 无码任你躁久久久久久老妇App| 综合久久一区二区三区 | 久久久精品人妻一区二区三区蜜桃| 午夜精品久久久久久久无码| 国内精品久久久久久久涩爱| 久久免费视频观看| 久久久久久综合一区中文字幕| 国产精品99久久免费观看| 久久国产精品成人片免费| 97久久久精品综合88久久| 久久精品一区二区三区不卡| 久久精品www|