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

            Shuffy

            不斷的學(xué)習(xí),不斷的思考,才能不斷的進(jìn)步.Let's do better together!
            posts - 102, comments - 43, trackbacks - 0, articles - 19
            [轉(zhuǎn)]http://m.shnenglu.com/tiandejian/archive/2007/09/13/ec_27.html

            第27條:     盡量不要使用類型轉(zhuǎn)換

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

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

            讓我們從復(fù)習(xí)轉(zhuǎn)型的語(yǔ)法開始,因?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ū)別。這僅僅是你把括號(hào)放在哪兒的問(wèn)題。我把這兩種形式稱為“懷舊風(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 通常用來(lái)脫去對(duì)象的恒定性。 C++ 風(fēng)格轉(zhuǎn)型中只有它能做到這一點(diǎn)。

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

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

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

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

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

            class Widget {

            public:

             explicit Widget(int size);

             ...

            };

             

            void doSomeWork(const Widget& w);

             

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

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

             

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

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

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

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

            int x, y;

            ...

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

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

            class Base { ... };

             

            class Derived: public Base { ... };

             

            Derived d;

             

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

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

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

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

            關(guān)于轉(zhuǎn)型的一件有趣事情是:你很容易編寫一些 “看上去正確”的東西,但實(shí)際上它們是錯(cuò)誤的。舉例說(shuō),許多應(yīng)用程序框架需要在派生類中實(shí)現(xiàn)一個(gè)虛擬成員函數(shù),并首先讓這些函數(shù)去調(diào)用基類中對(duì)應(yīng)的函數(shù)。假設(shè)我們有一個(gè) Window 基類和一個(gè) SpecialWindow 派生類,兩者都定義了虛函數(shù) onResize 。繼續(xù)假設(shè): SpecialWindow onResize 首先會(huì)調(diào)用 Window onResize 。以下是實(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ì)帶來(lái)任何影響。)如果一切如你所愿,代碼將會(huì) *this 轉(zhuǎn)型為一個(gè) Window ,同時(shí)此過(guò)程帶來(lái)一次 onResize 的調(diào)用,將會(huì)是 Window::onResize 。你一定沒有想到,當(dāng)前對(duì)象并沒有調(diào)用這一函數(shù)。取而代之的是,轉(zhuǎn)型過(guò)程創(chuàng)建了一個(gè)新的, *this 中基類部分的一個(gè)臨時(shí)副本,然后調(diào)用這一副本的 onResize 。上面的代碼將不會(huì)調(diào)用當(dāng)前對(duì)象的 Window::onResize ,然后進(jìn)行對(duì)象中的具體到 SpecialWindow 的動(dòng)作;而是再對(duì)當(dāng)前對(duì)象進(jìn)行 SpecialWindow 行為之前,去調(diào)用當(dāng)前對(duì)象的基類部分的副本中的 Window::onResize 。如果 Window::onResize 希望修改當(dāng)前對(duì)象(這也不是完全不可行,因?yàn)?/span> onResize 是一個(gè)非 const 的成員函數(shù)),實(shí)際上當(dāng)前對(duì)象不會(huì)受到任何影響。取而代之的是,這一對(duì)象的那個(gè)副本將會(huì)被修改。然而,如果 SpecialWindow::onResize 希望修改當(dāng)前對(duì)象,當(dāng)前對(duì)象將會(huì)被修改,這將導(dǎo)致下面的情景:代碼將會(huì)使當(dāng)前對(duì)象處于病態(tài)之中——它基類部分的修改沒有進(jìn)行,而派生類部分的修改卻完成了。

            解決方案就是:避免轉(zhuǎn)型。轉(zhuǎn)而使用你真正需要的類型。你并不希望欺騙編譯器將一個(gè) *this 識(shí)別為一個(gè)基類對(duì)象;你需要做的是:對(duì)當(dāng)前對(duì)象調(diào)用 onResize 的基類版本。所以你應(yīng)該這樣編寫:

            class SpecialWindow: public Window {

            public:

             virtual void onResize() {

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

                ...

             }

             ...

             

            };

            這個(gè)示例同時(shí)告訴我們:如果你需要進(jìn)行轉(zhuǎn)型,上面的代碼就會(huì)發(fā)出警告:你可能正在以錯(cuò)誤的方式工作。尤其是 dynamic_cast

            在深入探究 dynamic_cast 的實(shí)現(xiàn)設(shè)計(jì)方式之前,有必要先了解一下大多數(shù) dynamic_cast 的實(shí)現(xiàn)運(yùn)行的速度是非常緩慢的。比如說(shuō),至少有一種通用實(shí)現(xiàn)是通過(guò)比較各個(gè)類名的字符串。如果你正在針對(duì)一個(gè)四層深的單一繼承層次結(jié)構(gòu)中的一個(gè)對(duì)象進(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)鏈接)。在對(duì)性能要求較高的代碼中,要在整體上時(shí)刻對(duì)轉(zhuǎn)型持謹(jǐn)慎的態(tài)度,你應(yīng)該特別謹(jǐn)慎地使用 dynamic_cast 。

            一般說(shuō)來(lái),在你期望對(duì)那些你確認(rèn)屬于派生類的對(duì)象進(jìn)行派生類操作,但此時(shí)你只有一個(gè)指針或者一個(gè)指向基類的引用能操作這一對(duì)象, dynamic_cast 將派上用場(chǎng)。一般有兩條途徑來(lái)避免這一問(wèn)題。

            首先,可以使用容器來(lái)保存直接指向派生類對(duì)象的指針(通常是指能指針,參見第 13 條),這樣就在對(duì)這些對(duì)象進(jìn)行操作時(shí)就無(wú)需通過(guò)基類接口。比如說(shuō),在我們的 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 的信息請(qǐng)參見第 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();                 // 無(wú)需 dynamic_cast

                 ++iter)

             (*iter)->blink();

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

            一個(gè)可行的替代方案是:在基類中提供虛函數(shù),然后按需配置。這樣對(duì)于所有可能的 Window 派生類型,你都可以通過(guò)基類接口來(lái)進(jìn)行操作了。比如說(shuō),盡管只有 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)

                                            // 請(qǐng)注意這里沒有 dynamic_cast

             (*iter)->blink();

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

            關(guān)于 dynamic_cast 有一件事情自始至終都要注意,那就是:避免級(jí)聯(lián)使用。就是說(shuō)要避免類似下面的代碼出現(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í)行代碼將是冗長(zhǎng)而性能低下的,并且十分脆弱,這是因?yàn)槊慨?dāng) Window 的類層次結(jié)構(gòu)有所改變時(shí),就需要檢查所有這樣的代碼,以斷定它們是否需要更新。(比如說(shuō),如果添加了一個(gè)新的派生類,上面的級(jí)聯(lián)操作中就需要添加一個(gè)新的條件判斷分支。)這樣的代碼還是由虛函數(shù)調(diào)用的方式取代為好。

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

            銘記在心

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

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

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



            [1] 在編寫一個(gè)程序時(shí),出于種種原因經(jīng)常會(huì)自動(dòng)關(guān)閉或出錯(cuò)。雖然操作系統(tǒng)沒出問(wèn)題,但考慮到下次仍可能遇到相同的問(wèn)題,操作系統(tǒng)就會(huì)把程序出錯(cuò)時(shí)的內(nèi)存( core )中的內(nèi)容轉(zhuǎn)移( dump )出來(lái)供調(diào)試人員參考。此為 core dump 。——譯注。

             

            [2] 前文中的 doSomeWork(Widget(15)) , 使用函數(shù)風(fēng)格轉(zhuǎn)型創(chuàng)建一個(gè) int Widget 。 —— 譯注

            欧美日韩中文字幕久久久不卡 | 欧洲性大片xxxxx久久久| 91亚洲国产成人久久精品网址| 久久艹国产| 亚洲精品美女久久久久99| 国产免费久久精品丫丫| 欧美激情一区二区久久久| 久久综合中文字幕| 久久久久久久精品妇女99| 精品国产一区二区三区久久| 欧美激情精品久久久久久久| 国产精品久久影院| 久久这里只有精品首页| 久久久精品免费国产四虎| 伊人久久精品无码二区麻豆| 国产—久久香蕉国产线看观看| 少妇精品久久久一区二区三区 | 久久综合久久综合亚洲| 久久亚洲精品人成综合网| 精品久久久一二三区| 91久久精品视频| 999久久久无码国产精品| 国产精品久久新婚兰兰| 久久91精品综合国产首页| 久久不射电影网| 亚洲成色WWW久久网站| 伊色综合久久之综合久久| 久久99精品国产麻豆婷婷| 亚洲欧美日韩精品久久| 青青青国产成人久久111网站| 久久天天躁狠狠躁夜夜躁2O2O| 99久久精品免费看国产一区二区三区 | 久久久久久久久久久久久久| 久久精品国产WWW456C0M| 国产精品美女久久久免费| 色综合久久中文色婷婷| 97久久精品人人澡人人爽| 狠狠色综合网站久久久久久久| 97久久精品人人做人人爽| 久久99精品国产麻豆蜜芽| 国内精品欧美久久精品|