• <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讀書筆記 條目9] 永遠(yuǎn)不要在構(gòu)造或析構(gòu)的過程中調(diào)用虛函數(shù)

            讓我們直切正題:在程序進(jìn)行構(gòu)造或析構(gòu)期間,你絕不能調(diào)用虛函數(shù),這是因為這樣的調(diào)用并不會按你所期望的執(zhí)行,即使能夠順利執(zhí)行,你也不會覺得十分舒服。如果你曾經(jīng)是一個Java或C#的程序員,并且在最近期望返回C++的懷抱,那么請你格外留意本條目,因為在這一問題上,C++與其他語言走的是完全不同的兩條路線。

            假設(shè)有一個股票交易模擬系統(tǒng),你為它編寫了一個類的層次化結(jié)構(gòu),其中包括實現(xiàn)購買、拋售等功能的類。這類交易應(yīng)該是可以審計的,這一點很重要,所以說每創(chuàng)建一次交易時,都應(yīng)該在日志中創(chuàng)建一條審計相關(guān)內(nèi)容的記錄。下面是一個看似合理的解決方案:

            class Transaction {                      // 所有交易的基類

            public:

              Transaction();

              virtual void logTransaction() const = 0; // 作類型相關(guān)的記錄

              ...

            };

             

            Transaction::Transaction()               // 基類構(gòu)造函數(shù)的實現(xiàn)

            {

              ...

              logTransaction();                       // 最后,記錄這次交易

            }

             

            class BuyTransaction: public Transaction { // 派生類

            public:

              virtual void logTransaction() const;   // 當(dāng)前類型交易是如何記錄的

              ...

            };

             

            class SellTransaction: public Transaction {    // 派生類

            public:

              virtual void logTransaction() const;   // 當(dāng)前類型交易是如何記錄的

              ...

            };

            請考慮一下在下邊的代碼運行時會發(fā)生什么:

            BuyTransaction b;

            很明顯的是此時BuyTransaction的構(gòu)造函數(shù)將被調(diào)用,但是,首先必須調(diào)用Transaction的構(gòu)造函數(shù)。對于一個派生的對象,其基類那一部分會首先得到構(gòu)造,然后才是派生類的部分。Transaction的構(gòu)造函數(shù)中最后一行調(diào)用了虛函數(shù)logTransaction,意外的事情就從這里發(fā)生了:此處調(diào)用的是Translation版本的logTransaction函數(shù),而不是BuyTransaction版本的——即使此處創(chuàng)建的對象是BuyTransaction類型的。在基類部分的構(gòu)造過程中,虛函數(shù)永遠(yuǎn)也不會嘗試去匹配派生類部分。取而代之的是,對象仍然保持基類的行為。更隨意一點的說法是,在基類部分構(gòu)造的過程中,虛函數(shù)并不會被構(gòu)造。

            這一行為看上去匪夷所思,但是這里有很充足的理由來解釋它。由于基類的構(gòu)造函數(shù)先于派生類運行,在基類構(gòu)造函數(shù)運行的時候,派生類的數(shù)據(jù)成員還沒有被初始化。如果在基類構(gòu)造函數(shù)向下匹配派生類時調(diào)用了虛函數(shù),那么基類的函數(shù)幾乎一定會調(diào)用局部數(shù)據(jù)成員,但此時這些數(shù)據(jù)成員此時尚未得到初始化。你的程序?qū)霈F(xiàn)無盡的未定義行為,你也會在整夜受到瑣碎的調(diào)試工作的折磨。當(dāng)一個對象中某些部分尚未初始化的時候,此時對其進(jìn)行調(diào)用會存在內(nèi)在的危險,所以C++不允許你這樣做。

            實際情況比上文介紹的更為基礎(chǔ)。對于一個派生類的對象來說,在其進(jìn)行基類部分構(gòu)造工作的時候,這一對象的類型就是基類的。不僅僅虛函數(shù)會解析為基類的,而且C++中“使用運行時類型信息”的部分(比如dynamic_cast(參見條目27)和typeid)也會將其看作基類類型的對象。在我們的示例中,當(dāng)調(diào)用Transaction的構(gòu)造函數(shù)以初始化一個BuyTransaction對象的基類部分時,這一對象是Transaction類型的。C++的任何一部分都會這樣處理,這種處理方式是有意義的:由于這個對象的BuyTransaction部分尚未得到初始化,所以假定它們不存在才是最安全的處理方法。對于一個派生類對象來說,只有派生類的構(gòu)造函數(shù)開始執(zhí)行,這個對象才會變成該派生類的對象。

            對于析構(gòu)過程可以應(yīng)用同樣的推理方式。一旦派生類的析構(gòu)函數(shù)運行完畢,對象中派生類的那一部分?jǐn)?shù)據(jù)成員將取得未定義的值,所以C++會認(rèn)為它們不再存在。在進(jìn)入基類的析構(gòu)函數(shù)時,這個對象將成為一個基類對象,C++的所有部分——包括虛函數(shù)、dynamic_cast等等——都會這樣對待該對象。

            在上文的示例代碼中,Transaction的構(gòu)造函數(shù)對一個虛函數(shù)進(jìn)行了一次直接調(diào)用,很顯然這樣做是違背本條中的指導(dǎo)方針的。這樣的違規(guī)實在太容易發(fā)現(xiàn)了,一些編譯器都會對其做出警告。(其他一些則不會。參見條目53對編譯器警告信息的討論)即使沒有警告,問題也一定會在運行之前變得很明顯,這是因為Transaction中的logTransaction函數(shù)是純虛函數(shù),除非它得到了定義(不像是真的,但存在這種可能,參見條目34),程序才有可能會得到連接,其他情況都會報錯:連接器無法找到必要的Transaction::logTransaction的具體實現(xiàn)。

            查找構(gòu)造或析構(gòu)過程中對虛函數(shù)的調(diào)用并不總是一帆風(fēng)順的。如果Transaction擁有多個構(gòu)造函數(shù),它們所進(jìn)行的工作中有一部分是相同的,那么可以將這些公共的初始化代碼(包括對logTransaction的調(diào)用)放入一個私有的非虛擬的初始化函數(shù)中,這樣做可以避免代碼重復(fù),從軟件工程角度來講這似乎是一個很好的做法,我們將這一函數(shù)命名為init

            class Transaction {

            public:

              Transaction()

              { init(); }                      // 調(diào)用非虛函數(shù)...

             

              virtual void logTransaction() const = 0;

              ...

             

            private:

              void init()

              {

                ...

                logTransaction();              // ...而它卻調(diào)用一個虛函數(shù)!

              }

            };

            這樣的代碼與前文中的版本使用的是同一理念,但是這樣做所帶來的危害更為隱蔽和嚴(yán)重,這是因為這樣的代碼會得到正常的編譯和連接而不會報錯。這種情況下,由于logTransactionTransaction中的一個純虛函數(shù),大多數(shù)運行時系統(tǒng)將會在調(diào)用這個純虛函數(shù)時中止程序(通常情況下會針對這一結(jié)果顯示出一個消息)。然而如果logTransaction是一個“正常的”虛函數(shù)(也就是說,不是純虛的),并且在Transaction中給出了一些實現(xiàn),那么此時將調(diào)用這一版本的logTranscation,程序?qū)?#8220;愉快地一路小跑”下去,至于為什么在創(chuàng)建派生類對象時會調(diào)用錯誤的logTransaction版本,程序可就不管這一套了。避免這類問題的唯一途徑就是:在正在創(chuàng)建或銷毀的對象的構(gòu)造函數(shù)和析構(gòu)函數(shù)中,確保永遠(yuǎn)不要調(diào)用虛函數(shù),對于構(gòu)造函數(shù)和析構(gòu)函數(shù)所調(diào)用的所有函數(shù)都應(yīng)遵守這一約定。

            那么,每當(dāng)創(chuàng)建一個Transaction層次結(jié)構(gòu)中的對象時,如何確保去調(diào)用正確的logTransaction版本呢?顯然地,在Transaction的構(gòu)造函數(shù)中調(diào)用一個虛函數(shù)是一個錯誤的做法。

            為解決這一問題我們可以另辟蹊徑。方案之一就是:將Transaction中的logTransaction變?yōu)橐粋€非虛函數(shù),然后要求派生類的構(gòu)造函數(shù)把必要的日志記錄傳遞給Transaction的構(gòu)造函數(shù)。這個構(gòu)造函數(shù)對于非虛logTransaction的調(diào)用就是安全的。就像這樣:

            class Transaction {

            public:

              explicit Transaction(const std::string& logInfo);

             

              void logTransaction(const std::string& logInfo) const;

              // 現(xiàn)在logTransaction是非虛函數(shù)

              ...

            };

             

            Transaction::Transaction(const std::string& logInfo)

            {

              ...

              logTransaction(logInfo);         // 現(xiàn)在調(diào)用的是一個非虛函數(shù)

            }

             

            class BuyTransaction: public Transaction {

            public:

             BuyTransaction( parameters )

             : Transaction(createLogString( parameters ))

              { ... }                          // 將記錄傳遞給基類構(gòu)造函數(shù)

             

               ...

             

            private:

              static std::string createLogString( parameters );

            };

            換句話說,你不能使用虛函數(shù)在基類構(gòu)造過程中向下調(diào)用派生類的部分,作為一種補(bǔ)償,你可以讓派生類將一些必要的構(gòu)造信息向上傳遞給基類的構(gòu)造函數(shù)。

            請注意上述示例里BuyTransaction類中(私有的)靜態(tài)函數(shù)createLogString的使用。這里使用了一個輔助函數(shù)創(chuàng)建一個值來傳遞給基類的構(gòu)造函數(shù),通常情況下這樣做更為方便(而且更具備可讀性),這樣做使為基類提供所需信息的成員初始化表變得更加直觀。這是因為這樣做解決了“為基類提供所需信息的成員初始化表”不直觀的問題。意外調(diào)用初生的BuyTransaction對象中那些尚未初始化的數(shù)據(jù)成員是十分危險的,由于createLogString是靜態(tài)的,此處便不存在這一危險。這一點很重要,因為這些數(shù)據(jù)成員正處于未定義的狀態(tài),這一事實便解釋了為什么“在基類部分構(gòu)造或析構(gòu)期間調(diào)用虛函數(shù),不會在第一時間向下匹配派生類”。

            時刻牢記

            不要在構(gòu)造和析構(gòu)的過程中調(diào)用虛函數(shù),因為這樣的調(diào)用永遠(yuǎn)不會轉(zhuǎn)向當(dāng)前執(zhí)行的析構(gòu)函數(shù)或構(gòu)造函數(shù)更深層的派生類中執(zhí)行。

            posted on 2007-04-27 22:37 ★ROY★ 閱讀(1516) 評論(4)  編輯 收藏 引用 所屬分類: Effective C++

            評論

            # re: 【翻譯】Effective C++ (第9條:永遠(yuǎn)不要在構(gòu)造或析構(gòu)的過程中調(diào)用虛函數(shù))  回復(fù)  更多評論   

            不錯, 最近也在看Effective C++
            2007-04-28 13:01 | Galaxy

            # re: 【翻譯】Effective C++ (第9條:永遠(yuǎn)不要在構(gòu)造或析構(gòu)的過程中調(diào)用虛函數(shù))[未登錄]  回復(fù)  更多評論   

            不是嚴(yán)格按照effective c++的那些item順序來翻譯的吧~

            是說怎么不對應(yīng)呢
            2007-04-29 11:58 | recorder

            # re: 【翻譯】Effective C++ (第9條:永遠(yuǎn)不要在構(gòu)造或析構(gòu)的過程中調(diào)用虛函數(shù))  回復(fù)  更多評論   

            我翻的是,Effective C++第三版,您看的可能是第二版。所以有些不一樣。第三版比第二版要難一些。像/**/和//哪個好這樣的問題第三版中就忽略了。Meyers先生可能還會寫第四版,估計那時候難度又要上一個臺階。
            2007-04-29 16:55 | ★ROY★

            # re: 【翻譯】Effective C++ (第9條:永遠(yuǎn)不要在構(gòu)造或析構(gòu)的過程中調(diào)用虛函數(shù))[未登錄]  回復(fù)  更多評論   

            呵呵,是說呢。寫得不錯,繼續(xù)努力~ 偶會一直關(guān)注~
            2007-05-05 17:59 | recorder
            国产成人精品综合久久久| 91精品国产综合久久四虎久久无码一级| 99久久99久久精品国产| 狠狠色丁香婷综合久久| 久久综合伊人77777麻豆| 久久精品国产2020| 狠狠狠色丁香婷婷综合久久五月| 99久久99久久久精品齐齐 | 品成人欧美大片久久国产欧美...| 久久精品国产影库免费看| 亚洲?V乱码久久精品蜜桃| 国内精品伊人久久久久av一坑 | 亚洲美日韩Av中文字幕无码久久久妻妇| 久久夜色撩人精品国产| 亚洲中文字幕久久精品无码喷水| 久久精品人人做人人爽电影| 四虎亚洲国产成人久久精品| 午夜不卡久久精品无码免费| 久久精品亚洲乱码伦伦中文| 国产精品久久久久AV福利动漫| 久久强奷乱码老熟女网站| 狠狠色婷婷久久一区二区三区| 日韩亚洲国产综合久久久| 九九99精品久久久久久| 无码AV中文字幕久久专区| 久久人人爽人人爽人人片AV东京热| 99国产精品久久久久久久成人热| 少妇被又大又粗又爽毛片久久黑人 | 久久精品国产清高在天天线| 亚洲国产成人久久精品99| 久久精品国产免费一区| 久久精品国产亚洲AV麻豆网站| 一本久久a久久精品亚洲| 亚洲另类欧美综合久久图片区| 久久久久亚洲AV无码专区网站 | 久久青青国产| 久久亚洲欧洲国产综合| 亚洲国产精品综合久久网络 | 亚洲色欲久久久综合网| 久久久久亚洲AV成人网人人网站| 久久久99精品成人片中文字幕|