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

洛譯小筑

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

[ECPP讀書筆記 條目7] 要將多態基類的析構函數聲明為虛函數

現在考慮一個計時器的問題,我們首先創建一個名為TimeKeeper的基類,然后在它的基礎上創建各種派生類,從而用不同手段來計時。由于計時有很多方式,所以這樣做是值得的:

class TimeKeeper {

public:

  TimeKeeper();

  ~TimeKeeper();

  ...

};

 

class AtomicClock: public TimeKeeper { ... };  // 原子鐘

class WaterClock: public TimeKeeper { ... };   // 水鐘

class WristWatch: public TimeKeeper { ... };   // 腕表

許多客戶希望在訪問時間時不用關心計算的細節,所以在此可以使用一個工廠函數來返回一個指向計時器對象的指針,工廠函數會返回一個基類指針,這個指針將指向一個新創建的派生類對象:

TimeKeeper* getTimeKeeper();      // 返回一個繼承自TimeKeeper的動態分配的對象

為了不破壞工廠函數的慣例,getTimeKeeper返回的對象將被放置在堆上,所以必須要在適當的時候刪除每一個返回的對象,從而避免內存或者其他資源發生泄漏:

TimeKeeper *ptk = getTimeKeeper(); // TimeKeeper層取得

                                   // 一個動態分配的對象

 

...                                // 使用這個對象

 

delete ptk;                        // 釋放它,以防資源泄漏

把釋放工作推卸給客戶將會帶來出錯的隱患,條目13中解釋了這一點。關于如何修改工廠函數的接口從而防止一般的客戶端錯誤發生,請參見條目18。但是這些議題在此都不是主要的,這一條目中我們主要討論的是上文中的代碼存在著的一個更為基本的弱點:即使客戶把每一件事都做得很完美,我們仍無法預知程序會產生怎樣的行為。

現在的問題是:getTimeKeeper返回一個指向某個派生類對象的指針(比如說AtomicClock),這個對象最終會通過一個基類指針得到刪除(比如說TimeKeeper*指針),而基類(TimeKeeper)有一個非虛析構函數。這里埋藏著災難,這是因為C++有明確的規則:如果希望通過一個指向基類的指針來刪除一個派生類對象,并且這一基類有一個非虛析構函數,結果將是未定義的。典型的后果就是,在運行時,派生類中新派生出的部分得不到銷毀。如果getTimeKeeper返回了一個指向AtomicClock對象的指針,那么這一對象中AtomicClock的部分(也就是AtomicClock類中新聲明的數據成員)有可能不會被銷毀掉,AtomicClock的析構函數也可能不會得到運行。然而,這一對象中基類那一部分(也就是TimeKeeper的部分)很自然的會被銷毀掉,這樣便會產生一個古怪的“部分銷毀”的對象。用這種方法來泄漏資源、破壞數據結構、浪費調試時間,實在是“再好不過”了。

排除這一問題的方法很簡單:為基類提供一個虛擬的析構函數。這時刪除一個派生類對象,程序就會精確地按你的需要運行了。整個對象都會得到銷毀,包括所有新派生的部分:

class TimeKeeper {

public:

  TimeKeeper();

  virtual ~TimeKeeper();

  ...

};

 

TimeKeeper *ptk = getTimeKeeper();

...

delete ptk;                        // 現在,程序正常運轉

通常情況下,像TimeKeeper這樣的基類會包含除析構函數以外的虛函數,這是因為虛函數的目的是允許派生類實現中對它們進行自定義(參見條目34)。比如說,TimeKeeper類中可能存在一個虛函數getCurrentTime,它在不同的派生類中將有不同的實現方式。任何有虛函數的類幾乎一定都要包含一個虛析構函數。

如果一個類不包含虛函數,通常情況下意味著它將不作為基類使用。當一個類不作為基類時,將它的析構函數其聲明為虛擬的通常情況下不是個好主意。請看下面的示例,這個類代表二維空間中的點:

class Point {                      // 2D的點

public:

  Point(int xCoord, int yCoord);

  ~Point();

 

private:

  int x, y;

};

在一般情況下,如果一個int占用32比特,一個Point對象便可以置入一個64位的寄存器中。而且,這樣的一個Point對象可以以一個64位數值的形式傳給其他語言編寫的函數,比如C或者FORTRAN。然而如果Point的析構函數是虛擬的,那么就是另一種情況了。

虛函數的實現需要它所在的對象包含額外的信息,這一信息用來在運行時確定本對象需要調用哪個虛函數。通常,這一信息采取一個指針的形式,這個指針被稱為“vptr”(“虛函數表指針”,virtual table pointer)。vptr指向一個包含函數指針的數組,這一數組稱為“vtbl”(“虛函數表”,virtual table),每個包含虛函數的類都有一個與之相關的vtbl。當一個虛函數被一個對象調用時,就用到了該對象的vptr所指向的vtbl,在vtbl中查找一個合適的函數指針,然后調用相應的實函數。

虛函數實現的細節并不重要。重要的僅僅是,如果Point類包含一個虛函數,這一類型的對象將會變大。在一個32位的架構中,Point對象將會由64位(兩個int大小)增長至96位(兩個int加一個vptr);在64位架構中,Point對象將由64位增長至128位。這是因為指向64位架構的指針有64位大小。可以看到,為Point添加一個vptr將會使對象增大50-100%!這樣,一個64位的寄存器便容不下一個Point對象了。而且,此時C++版本的Point對象便不再與其它語言(比如C語言)有同樣的結構,這是因為其它語言很可能沒有vptr的概念。于是,除非你顯式增補一個vptr的等價物(但這是這種語言的實現細節,而且不具備可移植性),否則Point對象便無法與其它語言編寫的函數互通。

不得不承認,無故將所有的析構函數聲明為虛擬的,與從不將它們聲明為虛函數一樣糟糕,這一點至關重要。實際上,許多人總結出一條解決途徑:當且僅當類中至少包含一個虛函數時,要聲明一個虛析構函數。

甚至在完全沒有虛函數的類里,你也可能會被非虛擬的構造函數所糾纏。比如說,雖然標準的string類型不包含虛函數,但是誤入歧途的程序員有些時候還是會將其作為基類:

class SpecialString: public std::string {

                                   // 這不是個好主意!

                                   // std::string 有一個非虛擬的析構函數

  ...

};

乍一看,這樣的代碼似乎沒什么問題,但是如果在某應用程序里,你不知出于什么原因希望將一個SpecialString指針轉型為string指針,同時你又對這個string指針使用了delete,你的程序會立刻陷入未定義行為:

SpecialString *pss =   new SpecialString("Impending Doom");

 

std::string *ps;

...

 

ps = pss;                          // SpecialString* std::string*

...

 

delete ps;                         // 未定義行為!在實踐中*psSpecialString

                                   // 部分資源將會泄漏,這是因為SpecialString

                                   // 的析構函數沒有被調用。

對于任意沒有虛析構函數的類而言,上面的分析都成立,包括所有的STL容器類型(比如vectorlistsettr1::unordered_map(參見條目54),等等)。如果你曾經繼承過一個標準容器或者其他任何包含非虛析構函數的類,一定要打消這種想法!(遺憾的是,C++沒有提供類似Java中的final類或C#中的sealed類那種防止繼承的機制)

在個別情況下,為一個類提供一個純虛析構函數是十分方便的。你可以回憶一下,純虛函數會使其所在的類成為抽象類——這種類不可以實例化(也就是說,你無法創建這種類型的對象)。然而某些時刻,你希望一個類成為一個抽象類,但是你有沒有任何純虛函數,這時候要怎么辦呢?因為抽象類應該作為基類來使用,而基類應該有虛析構函數,又因為純虛函數可以造就一個抽象類,那么解決方案就顯而易見了:如果你希望一個類成為一個抽象類,那么在其中聲明一個純虛析構函數。下邊是示例:

class AWOV {                       // AWOV = "Abstract w/o Virtuals"

public:

  virtual ~AWOV() = 0;             // 聲明純虛析構函數

};

這個類有一個純虛函數,所以它是一個抽象類,同時它擁有一個虛析構函數,所以你不需要擔心析構函數的問題。然而這里還是有一個別扭的地方:你必須為純虛析構函數提供一個定義:

AWOV::~AWOV() {}                   // 純虛析構函數的定義

析構函數的工作方式是這樣的:首先調用最后派生出的類的析構函數,然后依次調用上一層基類的析構函數。由于當調用一個AWOV的派生類的析構函數時,編譯器會自動調用~AWOV,因此你必須為~AWOV提供一個函數體。否則連接器將會報錯。

為基類提供虛析構函數的原則僅對多態基類(這種基類允許通過其接口來操控派生類的類型)有效。我們說TimeKeeper是一個多態基類,這是由于即使我們手頭只有TimeKeeper指向它們的指針,我們仍可以對AtomicClockWaterClock進行操控。

并不是所有的基類都要具有多態性。比如說,標準string類型、STL容器都不用作基類,因此它們都不具備多態性。另外有一些類是設計用作基類的,但是它們并未被設計成多態類。這些類(例如條目6中的Uncopyable和標準類中的input_iterator_tag(參見條目47))不允許通過其接口來操控它的派生類。因此,它們并不需要虛析構函數。

時刻牢記

應該為多態基類聲明虛析構函數。一旦一個類包含虛函數,它就應該包含一個虛析構函數。

如果一個類不用作基類或者不需具有多態性,便不應該為它聲明虛析構函數。

posted on 2007-04-20 21:59 ★ROY★ 閱讀(1384) 評論(2)  編輯 收藏 引用 所屬分類: Effective C++

評論

# re: 【翻譯】Effective C++ (第7條:要將多態基類的析構函數聲明為虛函數)  回復  更多評論   

謝謝,對我非常有幫助~
2008-09-25 16:13 | wb

# re: 【翻譯】Effective C++ (第7條:要將多態基類的析構函數聲明為虛函數)  回復  更多評論   

3ks
2009-06-24 16:14 | Felix021
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久热re这里精品视频在线6| 欧美精品三级| 亚洲激情第一页| 久久精品麻豆| 久久女同精品一区二区| 美脚丝袜一区二区三区在线观看| 久久精品国产清高在天天线| 久久夜色精品国产欧美乱极品| 欧美h视频在线| 91久久久久| 一本色道久久综合亚洲精品高清| 99亚洲伊人久久精品影院红桃| 亚洲一区二区免费在线| 久久激情婷婷| 欧美久久99| 国产日韩欧美制服另类| 亚洲国产精品一区二区三区| 一区二区三区四区五区视频| 久久精品成人一区二区三区| 亚洲高清在线观看一区| 一本一道久久综合狠狠老精东影业| 亚洲香蕉网站| 欧美99在线视频观看| 国产精品视频免费一区| 在线观看免费视频综合| 在线亚洲电影| 欧美ed2k| 亚洲欧美日韩国产综合在线| 你懂的国产精品| 国产欧美日韩一区二区三区在线| 亚洲国产精品一区制服丝袜| 午夜精品一区二区三区四区 | 性欧美大战久久久久久久久| 久久一本综合频道| 国产精品99久久久久久久久久久久 | 欧美日本高清视频| 国模一区二区三区| 一区二区三区四区五区精品| 乱人伦精品视频在线观看| 一区二区免费在线播放| 欧美成人一区二区三区| 一区二区亚洲| 久久久久在线观看| 亚洲免费中文| 国产精品久久久久久影视| 日韩视频在线一区二区| 欧美成人网在线| 久久精品国产99| 国产女主播在线一区二区| 一本色道久久综合亚洲精品婷婷 | 夜夜躁日日躁狠狠久久88av| 免费成人高清在线视频| 一区免费观看视频| 久久久欧美一区二区| 性欧美超级视频| 国产精品视频免费一区| 亚洲欧美日韩精品久久| 亚洲午夜视频在线| 国产精品久久国产精品99gif | 欧美成人嫩草网站| 久久久久www| 红桃av永久久久| 久久蜜桃精品| 久久久天天操| 亚洲高清网站| 亚洲电影av在线| 欧美国产一区二区| 一本色道久久综合亚洲精品小说| 99视频热这里只有精品免费| 国产精品久久二区| 久久国产精品久久w女人spa| 欧美呦呦网站| 亚洲国产成人久久综合| 亚洲欧洲日本专区| 国产精品www网站| 欧美一区1区三区3区公司| 欧美一区国产一区| 在线欧美视频| 亚洲国产精选| 欧美日韩一区二区三区免费| 亚洲欧洲av一区二区| 久久www成人_看片免费不卡| 亚洲丰满在线| 99国产精品久久久久久久久久| 国产精品嫩草久久久久| 久久综合久久综合久久综合| 欧美国产91| 欧美一级片在线播放| 久久亚洲一区二区| 欧美黑人国产人伦爽爽爽| 亚洲一区二区三区视频| 久久成人亚洲| 一二三四社区欧美黄| 香蕉久久夜色精品国产使用方法| 在线日本成人| 亚洲尤物精选| 亚洲精品社区| 久久国产精品99精品国产| 日韩视频在线一区二区三区| 久久久www成人免费毛片麻豆| 亚洲一级在线观看| 精品动漫一区二区| 日韩午夜中文字幕| 影音先锋亚洲电影| 亚洲最新在线视频| 一区二区在线视频播放| 中日韩视频在线观看| 亚洲欧洲精品一区二区三区不卡| 亚洲午夜精品久久久久久app| 亚洲国产精品va在线看黑人 | 激情91久久| 中文日韩在线| 亚洲欧洲精品一区二区三区不卡 | 亚洲激情综合| 狠狠色2019综合网| 亚洲视频自拍偷拍| 日韩视频在线一区二区| 久久视频在线免费观看| 久久精品日韩一区二区三区| 国产精品成人v| 亚洲精品国产拍免费91在线| 亚洲电影激情视频网站| 久久九九精品99国产精品| 欧美影视一区| 国产精品国产三级国产普通话99 | 亚洲精品美女在线观看播放| 亚洲成色777777女色窝| 久久精品国产精品亚洲| 久久精品国产99精品国产亚洲性色| 欧美性猛片xxxx免费看久爱| 亚洲精品久久视频| 亚洲每日更新| 欧美经典一区二区| 亚洲第一天堂无码专区| 亚洲激情午夜| 欧美国产日韩免费| 91久久国产精品91久久性色| 亚洲欧洲日本mm| 欧美好骚综合网| 日韩一级裸体免费视频| 亚洲视频图片小说| 午夜久久黄色| 欧美一区久久| 国内综合精品午夜久久资源| 久久九九久精品国产免费直播 | 一本色道久久综合亚洲精品不卡| 欧美成人a∨高清免费观看| 亚洲国产精品va在线看黑人| 亚洲激情综合| 欧美日韩精品在线播放| 在线一区二区三区四区五区| 欧美一级视频精品观看| 狠狠色伊人亚洲综合网站色| 美腿丝袜亚洲色图| 亚洲巨乳在线| 久久av免费一区| 伊人久久噜噜噜躁狠狠躁| 女人色偷偷aa久久天堂| 亚洲精品免费网站| 欧美一级片一区| 亚洲激情在线视频| 国产精品久久久一区二区三区| 性欧美xxxx大乳国产app| 亚洲成在线观看| 亚洲一区二区高清视频| 国产一区成人| 欧美另类videos死尸| 亚洲一区二区黄| 欧美不卡高清| 亚洲欧美日韩在线不卡| 亚洲电影免费观看高清完整版在线观看 | 精品91视频| 欧美日韩一级黄| 久久久久欧美精品| 日韩网站在线观看| 久久阴道视频| 亚洲一区二区三区在线看| 在线精品视频在线观看高清| 国产精品电影观看| 免费的成人av| 欧美一级视频一区二区| 亚洲欧洲综合另类| 久久久噜噜噜久久久| 亚洲一区二区三区在线播放| 亚洲国产精品综合| 国产在线一区二区三区四区| 欧美日韩国产在线播放| 久久综合激情| 欧美在线观看网站| 国产精品99久久久久久人| 亚洲国产一区二区a毛片| 久久久久网站| 欧美一区二区三区免费在线看| 99riav久久精品riav| 亚洲夫妻自拍| 影音先锋久久精品| 好吊日精品视频| 国产日韩欧美日韩| 国产精品一香蕉国产线看观看| 欧美日韩国产不卡|