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

Shuffy

不斷的學習,不斷的思考,才能不斷的進步.Let's do better together!
posts - 102, comments - 43, trackbacks - 0, articles - 19
[轉]http://m.shnenglu.com/tiandejian/archive/2007/04/20/ecpp_07.html

第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 ”(“虛函數表指針”)。 vptr 指向一個包含函數指針的數組,這一數組稱為“ vtbl ”(“虛函數表”),每個包含虛函數的類都有一個與之相關的 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;                     // 結果是無法預知的!在實踐中 *ps

                               // SpecialString 這一部分資源將會

                               // 泄漏,這是因為 SpecialString

                               // 析構器沒有被調用。

對于所有沒有虛析構器的類上面的分析也成立,包括所有的 STL 容器類型(比如 vector list set tr1::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 指向它們的指針,我們仍可以對 AtomicClock WaterClock 進行操控。

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

需要記住的

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

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

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            欧美ed2k| 欧美性视频网站| 欧美中文字幕在线| 欧美成人第一页| 久久一区二区三区av| 国产精品二区影院| 性刺激综合网| 亚洲二区精品| 美女国内精品自产拍在线播放| 一区二区三区四区五区精品视频| 亚洲欧洲日本mm| 久久伊伊香蕉| 欧美一区国产在线| 亚洲自拍另类| 国产日韩欧美在线看| 999在线观看精品免费不卡网站| 欧美不卡一区| 欧美日韩亚洲一区二区三区四区| 国产精品人成在线观看免费| 欧美大尺度在线| 免费视频一区| 一区二区三区久久久| 亚洲激情一区二区| 久久成人精品无人区| 亚洲欧美国产毛片在线| 欧美精品一区二区三区蜜桃| 国产精品亚洲一区| 欧美与欧洲交xxxx免费观看 | 久久久久久久久一区二区| 一区二区三区.www| 欧美亚洲成人网| 欧美在线三区| 欧美亚洲色图校园春色| 国产精品欧美风情| 亚洲在线一区| 亚洲精选成人| 久久成人国产| 亚洲美洲欧洲综合国产一区| 欧美刺激性大交免费视频| 1024亚洲| 欧美国产日产韩国视频| 欧美 日韩 国产一区二区在线视频 | 一区精品在线| 亚洲视频www| 欧美国产视频在线观看| 欧美第十八页| 亚洲视频一区| 亚洲欧美日韩国产成人| 国产日韩精品一区二区浪潮av| 亚洲欧美精品在线观看| 亚洲欧美成人| 影音先锋久久久| 亚洲国产第一| 欧美午夜精品伦理| 久久久久久成人| 欧美99在线视频观看| 国产精品99久久久久久有的能看| 亚洲一区综合| 在线精品在线| 欧美国产大片| 欧美日韩综合在线| 久久福利影视| 欧美激情视频免费观看| 亚洲欧美日韩久久精品| 久久福利精品| 亚洲人成人77777线观看| 一本色道久久综合亚洲二区三区| 国产精品日韩专区| 久色婷婷小香蕉久久| 欧美日本精品在线| 久久久噜噜噜久久中文字免| 欧美国产视频一区二区| 欧美亚洲一区| 男女激情久久| 欧美一区二视频| 欧美激情一区二区三区在线| 亚洲男人的天堂在线| 亚洲精品一区在线观看| 国产女主播一区二区| 母乳一区在线观看| 国产精品稀缺呦系列在线| 男男成人高潮片免费网站| 欧美午夜精彩| 欧美激情一区二区三区不卡| 国产精品一级久久久| 欧美激情五月| 国产一本一道久久香蕉| 日韩性生活视频| 亚洲丰满在线| 欧美一区二区日韩一区二区| 中日韩美女免费视频网站在线观看| 亚洲美女av网站| 国产视频久久| 99re66热这里只有精品4| 在线观看视频一区| 午夜精品久久久久久久蜜桃app| 日韩午夜电影av| 久久综合久久综合久久| 久久成人人人人精品欧| 欧美午夜久久| 在线视频欧美精品| 99在线精品免费视频九九视| 久久一区二区三区四区五区| 久久国产精品亚洲va麻豆| 欧美成人一区在线| 免费视频一区| 亚洲第一视频| 久久久亚洲综合| 久久亚洲国产精品一区二区| 国产亚洲第一区| 亚洲欧美一区二区三区久久| 午夜精品久久久久| 国产精品日日摸夜夜摸av| 亚洲欧美国产不卡| 小辣椒精品导航| 欧美韩国日本一区| 欧美国产日韩xxxxx| 在线精品视频一区二区| 老**午夜毛片一区二区三区| 欧美成人免费网站| 亚洲第一精品福利| 麻豆成人综合网| 亚洲国产一区二区a毛片| 野花国产精品入口| 欧美黑人国产人伦爽爽爽| 亚洲国产精品一区二区第一页| 日韩天天综合| 国产精品高潮呻吟久久av黑人| 在线亚洲自拍| 久久精品2019中文字幕| 伊人成年综合电影网| 久久亚洲综合色| 久久精品国产一区二区电影 | 亚洲国产综合91精品麻豆| 日韩一级精品视频在线观看| 欧美日本一区二区三区 | 国产精品老女人精品视频| 亚洲已满18点击进入久久| 久久久国产一区二区| 午夜精品久久久久久久99水蜜桃 | 毛片基地黄久久久久久天堂| 毛片一区二区三区| 欧美一区2区视频在线观看| 欧美精品久久久久a| 欧美成人免费一级人片100| 亚洲中无吗在线| 久久精品国产综合精品| 国产精品男人爽免费视频1| 国产精品99久久久久久人| 亚洲少妇自拍| 国产精品超碰97尤物18| 一本一本久久a久久精品综合妖精 一本一本久久a久久精品综合麻豆 | 一本久久综合亚洲鲁鲁五月天| 香蕉成人伊视频在线观看| 久久久久国色av免费看影院| 亚洲永久视频| 久久久久久久久久久成人| 精品不卡在线| 欧美另类一区二区三区| 在线中文字幕不卡| 欧美专区在线| 欧美国产视频日韩| 一区二区三区日韩在线观看| 国产日韩欧美高清免费| 久久午夜精品一区二区| 亚洲精品乱码久久久久久久久| 在线亚洲观看| 国产午夜精品在线| 久久看片网站| 欧美国产一区二区三区激情无套| 99精品国产福利在线观看免费| 国产精品二区影院| 久久久成人网| 一区二区三区四区在线| 久久亚洲精品一区| 亚洲精品你懂的| 欧美日本不卡| 欧美电影美腿模特1979在线看| 你懂的视频一区二区| 亚洲免费人成在线视频观看| 国产一区二区精品| 欧美日韩 国产精品| 亚洲摸下面视频| 亚洲国产福利在线| 一本色道久久88综合日韩精品| 亚洲精品一区二区网址| 国产精品一二一区| 欧美激情一区二区三区蜜桃视频 | 欧美日韩大片| 免费一级欧美片在线播放| 一区二区三区视频在线看| 国产一区二区三区在线观看精品 | 99精品国产在热久久下载| 欧美一区二视频| 亚洲精品美女91| 国产一区二区av| 国产精品成人国产乱一区| 亚洲免费成人| 欧美一级在线播放| 亚洲日本va在线观看|