<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
    數據加載中……

    C++箴言:絕不在構造或析構期調用虛函數

    你不應該在構造或析構期間調用虛函數,因為這樣的調用不會如你想象那樣工作,而且它們做的事情保證會讓你很郁悶。如果你轉為 Java 或 C# 程序員,也請你密切關注本文,因為在 C++ 急轉彎的地方,那些語言也緊急轉了一個彎。

      假設你有一套模擬股票處理的類層次結構,例如,購入流程,出售流程等。對這樣的處理來說可以核查是非常重要的,所以隨時會創建一個 Transaction 對象,將這個創建記錄在核查日志中是一個適當的要求。下面是一個看起來似乎合理的解決問題的方法:

    class Transaction { // base class for all
     public: // transactions
      Transaction();

      virtual void logTransaction() const = 0; // make type-dependent
      // log entry
      ...
    };

    Transaction::Transaction() // implementation of
    {
     // base class ctor
     ...
     logTransaction(); // as final action, log this
    } // transaction

    class BuyTransaction: public Transaction {
     // derived class
     public:
      virtual void logTransaction() const; // how to log trans-
      // actions of this type
      ...
    };

    class SellTransaction: public Transaction {
    // derived class
    public:
     virtual void logTransaction() const; // how to log trans-
     // actions of this type
    ...
    };

      考慮執行這行代碼時會發生什么:

    BuyTransaction b;

      很明顯 BuyTransaction 的構造函數會被調用,但是首先,Transaction 的構造函數必須先被調用,派生類對象中的基類部分先于派生類部分被構造。Transaction 的構造函數的最后一行調用虛函數 logTransaction,但是結果會讓你大吃一驚,被調用的 logTransaction 版本是在 Transaction 中的那個,而不是 BuyTransaction 中的——即使被創建的對象類型是 BuyTransaction?;悩嬙炱陂g,虛函數從來不會向下匹配(go down)到派生類。取而代之的是,那個對象的行為就好像它的類型是基類。非正式地講,基類構造期間,虛函數禁止。 這個表面上看起來匪夷所思的行為存在一個很好的理由。因為基類的構造函數在派生類構造函數之前執行,當基類構造函數運行時,派生類數據成員還沒有被初始化。如果基類構造期間調用的虛函數向下匹配(go down)到派生類,派生類的函數理所當然會涉及到本地數據成員,但是那些數據成員還沒有被初始化。這就會為未定義行為和悔之晚矣的調試噩夢開了一張通行證。調用涉及到一個對象還沒有被初始化的部分自然是危險的,所以 C++ 告訴你此路不通。

      在實際上還有比這更多的更深層次的原理。在派生類對象的基類構造期間,對象的類型是那個基類的。不僅虛函數會解析到基類,而且語言中用到運行時類型信息(runtime type information)的配件(例如,dynamic_cast和 typeid),也會將對象視為基類類型。在我們的例子中,當 Transaction 構造函數運行初始化 BuyTransaction 對象的基類部分時,對象的類型是 Transaction。C++ 的每一個配件將以如下眼光來看待它,并對它產生這樣的感覺:對象的 BuyTransaction 特有的部分還沒有被初始化,所以安全的對待它們的方法就是視若無睹。在派生類構造函數運行之前,一個對象不會成為一個派生類對象。

      同樣的原因也適用于析構過程。一旦派生類析構函數運行,這個對象的派生類數據成員就被視為未定義的值,所以 C++ 就將它們視為不再存在。在進入基類析構函數時,對象就成為一個基類對象,C++ 的所有配件——虛函數,dynamic_casts 等——都如此看待它。

      在上面的示例代碼中,Transaction 的構造函數直接調用了虛函數,對本 Item 的規則的違例是顯而易見的。這一違例是如此顯見,以致一些編譯器會給出警告。(其它的則不會)甚至除了這樣的警告之外,這一問題幾乎肯定會在運行之前暴露出來,因為 logTransaction 函數在 Transaction 中是一個純虛函數。除非它被定義(看似不可能,但確實可能),否則程序將無法連接:連接程序無法找到 Transaction::logTransaction 的必需的實現。

      在構造函數和析構函數中調用虛函數的問題并不總是如此容易被察覺。如果 Transaction 有多個構造函數,每一個都必須完成一些相同的工作,好的軟件工程為避免代碼重復,會將共用的初始化代碼,包括對 logTransaction 的調用,放入一個私有的非虛的初始化函數,叫做 init:

    class Transaction {
    public:
     Transaction()
     { init(); } // call to non-virtual...

     virtual void logTransaction() const = 0;
     ...

    private:
     void init()
     {
      ...
      logTransaction(); // ...that calls a virtual!
     }
    };

      這個代碼在概念上和早先那個版本相同,但是它更陰險,因為它很具代表性地會躲過編譯器和連接程序的抱怨。在這種情況下,因為 logTransaction 在 Transaction 中是純虛函數,大多數運行時系統在純虛函數被調用時,程序會異常中止(典型的結果就是給出一條信息)。然而,如果 logTransaction 是一個“常規的”虛函數(也就是說,非純的虛函數),而且在 Transaction 中有其實現,那個版本被調用,程序會繼續一路小跑,讓你想象不出為什么派生類對象創建的時候會調用 logTransaction 的錯誤版本。避免這個問題的唯一辦法就是確保在你的構造函數和析構函數中,決不在你創建或銷毀的對象上調用虛函數,構造函數和析構函數所調用的函數也要服從同樣的約束。

      但是,如何保證在任何時間 Transaction 層次結構中的對象被創建時,都能調用 logTransaction 的正確版本呢?顯然,在 Transaction 的構造函數中在這個對象上調用虛函數的做法是錯誤的。

      有不同的方法來解決這個問題。其中之一是將 Transaction 中的 logTransaction 轉變為一個非虛函數,這就需要派生類的構造函數將必要的日志信息傳遞給 Transaction 的構造函數。那個函數就可以安全地調用非虛的 logTransaction。如下:

    class Transaction {
    public:
     explicit Transaction(const std::string& logInfo);

     void logTransaction(const std::string& logInfo) const; // now a non-
     // virtual func
     ...
    };

    Transaction::Transaction(const std::string& logInfo)
    {
     ...
     logTransaction(logInfo); // now a non-
    } // virtual call

    class BuyTransaction: public Transaction {
    public:
     BuyTransaction( parameters )
     : Transaction(createLogString( parameters )) // pass log info
     { ... } // to base class
     ... // constructor

    private:
     static std::string createLogString( parameters );
    };

      換句話說,因為在基類的構造過程中你不能使用虛函數,就改為由派生類傳遞必要的構造信息給基類的構造函數作為補償。 在此例中,注意 BuyTransaction 中那個(私有的)static 函數 createLogString 的使用。使用一個輔助函數創建一個值傳遞給基類的構造函數,通常比通過在成員初始化列表給基類它所需要的東西更加便利(也更加具有可讀性)。將那個函數做成 static,就不會有偶然涉及到一個初生的 BuyTransaction 對象的仍未初始化的數據成員的危險。這很重要,因為實際上那些數據成員在一個未定義狀態,這就是為什么在基類構造和析構期間虛函數不能首先匹配到派生類的原因。

      Things to Remember

      ·在構造和析構期間不要調用虛函數,因為這樣的調用不會匹配到當前執行的構造函數或析構函數所屬的類的更深的派生層次。

    from: http://dev.yesky.com/441/2033941.shtml

    posted on 2005-10-23 22:01 weidagang2046 閱讀(298) 評論(0)  編輯  收藏 所屬分類: C/C++

    主站蜘蛛池模板: 国产无遮挡又黄又爽免费网站| 国产精品亚洲专区无码不卡| 中国黄色免费网站| 不卡精品国产_亚洲人成在线| 高潮内射免费看片| 免费又黄又爽的视频| 色妞www精品视频免费看| 国产精品久免费的黄网站| 无码色偷偷亚洲国内自拍| 免费成人av电影| 亚洲免费无码在线| 国产亚洲福利精品一区| 十九岁在线观看免费完整版电影| 久久夜色精品国产噜噜亚洲AV| 亚洲人成免费电影| 亚洲日本VA午夜在线电影| 免费国产a国产片高清网站| 免费中文字幕视频| 亚洲国产精品无码久久久秋霞2| 91精品国产免费| 亚洲综合一区国产精品| 亚洲精品国产自在久久| 可以免费观看的毛片| 亚洲中文字幕无码av在线| 性色av免费观看| 一区视频免费观看| 亚洲一区二区三区首页| 免费无码又黄又爽又刺激| 直接进入免费看黄的网站| 国产亚洲精品国产| 久九九精品免费视频| 国产精品无码亚洲一区二区三区| 国产亚洲一区二区三区在线不卡 | 人与动性xxxxx免费| 久久精品夜色国产亚洲av| 毛片a级毛片免费观看品善网| 又大又硬又粗又黄的视频免费看| 亚洲宅男永久在线| 免费a级毛片永久免费| 久久免费看少妇高潮V片特黄| 亚洲一卡2卡3卡4卡5卡6卡|