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

            牧光小院

            被約束的日日夜夜,停不下來的時(shí)間。

            只在多態(tài)基類中聲明虛析構(gòu)函數(shù)

            關(guān)于virtual desctructor的詳細(xì)討論。同樣來自于《Effective C++》3rd Edition。

            跟蹤時(shí)間是很平常的任務(wù),所以開發(fā)一個(gè)名為 TimeKeeper 的基類,并讓不同的派生類來實(shí)現(xiàn)不同的計(jì)時(shí)方法是很合理的事情:

            class TimeKeeper {

            public :

            ??? TimeKeeper();

            ??? ~TimeKeeper();

            ??? ...

            };

            ?

            class AtomicClock: public TimeKeeper { ... };

            class WaterClock: public TimeKeeper { ... };

            class WristWatch: public TimeKeeper{ ... };

            很多用戶都希望直接用這些類來計(jì)數(shù),而對(duì)于他們究竟是如何實(shí)現(xiàn)的并不關(guān)心。于是一個(gè)我們可以用一個(gè) Factory function ——?jiǎng)?chuàng)建一個(gè)派生類對(duì)象并返回一個(gè)基類指針的函數(shù)——返回一個(gè)指向 TimeKeeper 的指針。

            TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamically

            ???????????????????????? ? ??? // allocated object of a class derived

            ???????????????????????? ? ??? // from TimeKeeper

            通常, factory function 返回的對(duì)象都是創(chuàng)建在堆上的,當(dāng)用戶使用完計(jì)數(shù)器的時(shí)候把對(duì)象析構(gòu)掉是很重要的:

            TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object

            ??????????????????????????? ?? ????? // from TimeKeeper hierarchy

            ...???????????????????????? ?? ?????// use it

            delete ptk;???????????????????? ?? // release it to avoid resource leak

            但是,依賴用戶來執(zhí)行刪除是錯(cuò)誤的重要來源。條款 18 介紹了如何修改 Factory function 的接口來避免這些常見的用戶錯(cuò)誤,但是,這些目前都是次要的,因?yàn)樵谏厦娴拇a中還存在更為嚴(yán)重的問題:即使客戶執(zhí)行的正確的動(dòng)作,你還是無法預(yù)期你的程序能夠正確執(zhí)行。

            問題在于 getTimeKeeper 返回了一個(gè)派生類對(duì)象(例如 :AutoicClock ),但是這個(gè)對(duì)象卻通過基類的指針來刪除(一個(gè)指向 Timekeeper 的指針),并且這個(gè)基類沒有虛析構(gòu)函數(shù)。這種組合是制造災(zāi)難的良方,因?yàn)?/span> C++ 規(guī)定:用不帶有虛析構(gòu)函數(shù)的基類的指針來刪除一個(gè)派生類,其結(jié)果是未定的。通常在運(yùn)行時(shí)發(fā)生的情況是這個(gè)對(duì)象的派生類部分沒有被析構(gòu)。如果 getTimeKeeper 返回一個(gè)指向 AtomicClock 對(duì)象的指針,那么 AtomicClock 中派生類的部分(例如在 AtomicClock 中聲明的數(shù)據(jù)成員)將不會(huì)被正確的析構(gòu),實(shí)際上 AtomicClock 的析構(gòu)函數(shù)都根本不會(huì)被調(diào)用。但是,基類的部分,卻會(huì)被正確的清除,這就造就了一個(gè)“畸形”的 partially destroyed object 。這是一個(gè)非常棒的泄漏資源、破壞數(shù)據(jù)的方法,它會(huì)讓你在調(diào)試器上花費(fèi)大量的精力。

            解決這個(gè)問題的方法很簡(jiǎn)單,給派生類加上一個(gè)虛析構(gòu)函數(shù)。這樣派生類對(duì)象就會(huì)如你所愿,被正確的清除:

            class TimeKeeper {

            public :

            ??? TimeKeeper();

            ??? virtual ~TimeKeeper();

            ??? ...

            };

            ?

            TimeKeeper *ptk = getTimeKeeper();

            ...???????????????????????????
            delete ptk;???????????????? // now behaves correctlhy

            TimeKeeper 這樣的基類,除了析構(gòu)函數(shù)外,通常會(huì)包含其它的虛函數(shù)。因?yàn)樘摵瘮?shù)的目標(biāo)就是讓派生類來訂制基類的實(shí)現(xiàn)。例如, getCurrentTime ,在不同的派生類中就會(huì)有不同的實(shí)現(xiàn)(注:其實(shí) getTimeKeeper 也可以是一個(gè)虛函數(shù))。任何一個(gè)擁有虛函數(shù)的類都應(yīng)該包含一個(gè)虛析構(gòu)函數(shù)。

            如果一個(gè)類沒有虛函數(shù)呢,這也就意味著這個(gè)類并不是被當(dāng)作基類來使用的。當(dāng)遇到這種情況的時(shí)候,聲明一個(gè)虛析構(gòu)函數(shù)往往不是一個(gè)好主意。考慮一個(gè)用來表示二維空間中的某點(diǎn)的類:

            class Point {// a 2D point

            public :

            ??? Point(int xCoord, int yCoord);

            ??? ~Point();

            ?

            private :

            ??? int x, y;
            };

            如果一個(gè) int 32 bits ,這樣的一個(gè) Point 可以被放到一個(gè) 64 位寄存器中。另外,這樣的一個(gè) Point 對(duì)象還可以被當(dāng)作是一個(gè)整體被其它的語言使用,例如 C FORTRAN 。但是,如果 Point 的析構(gòu)函數(shù)是虛擬的,故事就完全不一樣了。

            虛函數(shù)的實(shí)現(xiàn)需要對(duì)象承載某些額外信息,這些信息用來在運(yùn)行時(shí)對(duì)虛函數(shù)的調(diào)用進(jìn)行正確的轉(zhuǎn)發(fā)。這個(gè)額外的信息使通過一個(gè) vtpr 來實(shí)現(xiàn)的。 Vptr 指向一個(gè)存放函數(shù)指針( vtbl )的數(shù)組,每一個(gè)具有虛函數(shù)的類都有一個(gè)對(duì)應(yīng)的 vtbl 。當(dāng)一個(gè)對(duì)象的虛函數(shù)被調(diào)用的時(shí)候,該對(duì)象的 vtpr vtbl 組合來完成定位正確的函數(shù)調(diào)用的工作。

            這里,虛函數(shù)如何實(shí)現(xiàn)的并不重要。重要的是如果 Point 包含了一個(gè)虛函數(shù),對(duì)象將會(huì)長(zhǎng)胖。在一個(gè) 32 bits 的機(jī)器上,它將會(huì)從 64 bits 長(zhǎng)到 96 bits ;在 64 bit 的機(jī)器上,它將會(huì)從 64 bits 長(zhǎng)到 128 bits 。這個(gè)額外的 vtpr 的存在讓對(duì)象的體積增長(zhǎng)了 50%~100% Point 對(duì)象也不再能夠放到一個(gè) 64-bit 寄存器中了。另外, Point 對(duì)象也不再和 C 語言的保持兼容,因?yàn)?/span> C 語言中沒有 vrpr 機(jī)制。結(jié)果是,你要想使用該 Point 對(duì)象,除非自己來實(shí)現(xiàn) vtpr vtpl 機(jī)制,而這樣做,往往又會(huì)降低你的代碼的可移植性。

            也就是說,把所有的析構(gòu)函數(shù)都不加思索的聲明為虛擬的和從不把它們聲明為虛擬的一樣,都是不明智的行為。實(shí)際上,很多人得除了這樣的結(jié)論:當(dāng)且僅當(dāng)一個(gè)類有至少一個(gè)虛函數(shù)的時(shí)候,才把析構(gòu)函數(shù)聲明為虛擬的。

            實(shí)際上,即使你的類中沒有虛函數(shù),你還是有可能被非虛析構(gòu)函數(shù)的問題咬上一口。例如 std::string 就沒有虛函數(shù),但是一些被誤導(dǎo)的程序員有時(shí)會(huì)把它當(dāng)作基類來使用:

            class SpecialString: public std::string {

            // bad idea! std::string has a

            ??? ...????????????????????? ??? // non-virtual destructor
            }

            乍一看,這可能沒什么問題,但是一旦你把一個(gè)指向 SpecialString 的指針轉(zhuǎn)換成一個(gè) string ,并用這個(gè)指針來刪除 SpecialString 對(duì)象的時(shí)候,你馬上就被帶進(jìn)了未定義行為的深潭。

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

            std::string *ps;

            ...

            ps = pss;? // SpecialString* --> std::string*

            ?

            delete ps;? // undefined! In practice, *ps's Specialstring resources

            ?????????? ? // will be leaked, because the SpecialString destructor won't??????? // be called

            同樣的結(jié)果還會(huì)出現(xiàn)在其它沒有虛析構(gòu)函數(shù)的類中,例如所有的 STL 容器類型(例如: vector, list, set, tr1::unordered_map 等等)。如果你曾經(jīng)對(duì)于從一個(gè)標(biāo)準(zhǔn)容器或其它帶有非虛析構(gòu)函數(shù)的類繼承,那么徹底打消這個(gè)想法。(不幸的是 C++ 沒有提供像 C#(sealed) Java(final) 類似的拒絕繼承的語言機(jī)制)

            有時(shí)候,把析構(gòu)函數(shù)設(shè)定為 pure virtual 是非常方便的。一個(gè) pure virtual 函數(shù)可以讓一個(gè)類成為抽象類。有時(shí),你可能需要讓你的類成為一個(gè) abstract class ,但是你一時(shí)又找不到合適的純虛函數(shù)。怎么辦呢?因?yàn)橐粋€(gè)抽象類往往是要被作為基類的,而一個(gè)基類往往又應(yīng)該有一個(gè)虛析構(gòu)函數(shù)。這樣一來:聲明一個(gè) pure virtual destructor 就是一個(gè)不錯(cuò)的主意。一箭雙雕。

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

            public :

            ??? virtual ~AWOV() = 0; // declare pure virtual destructor

            };

            這個(gè)類有一個(gè)純虛函數(shù),因此這是以個(gè)抽象基類,并且這個(gè)類有一個(gè)虛析構(gòu)函數(shù),這也使你遠(yuǎn)離了析構(gòu)函數(shù)的問題,唯一要注意的,就是一定要為純虛析構(gòu)函數(shù)提供一份實(shí)現(xiàn)。

            虛析構(gòu)函數(shù)的工作方式是從最深的派生類的析構(gòu)函數(shù)依次調(diào)用其基類的析構(gòu)函數(shù),編譯器會(huì)生成生成一個(gè)從派生類到基類的 ~AWOV 的調(diào)用。如果你沒有提供析構(gòu)函數(shù)的實(shí)現(xiàn),鏈接器就會(huì)抱怨錯(cuò)誤。

            所以,你只應(yīng)該把多態(tài)基類的析構(gòu)函數(shù)聲明為虛擬的。只有你想通過基類接口來操作派生類的時(shí)候,一個(gè)基類才是多態(tài)的。 TimeKeeper 就是一個(gè)多態(tài)基類,因?yàn)槲覀冃枰靡粋€(gè) TimeKeeper* 來操作 AtomicClock WaterClock 對(duì)象。

            另外,并不是所有的基類都要按照多態(tài)的方式來設(shè)計(jì)和使用。 Std::string STL 中的容器類型就都不具備多態(tài)性。一些類被設(shè)計(jì)成基類,但是卻不應(yīng)該按照多態(tài)的方式來使用,例如 input_iterator_tag 就是一個(gè)例子,你并不需要用基類接口來操縱派生類。結(jié)果是,他們也不需要虛擬析構(gòu)函數(shù)。

            時(shí)時(shí)刻刻讓自己記住

            l ???????? 應(yīng)該為多態(tài)基類聲明虛擬析構(gòu)函數(shù)。如果一個(gè)類有一個(gè)虛函數(shù),那么它也應(yīng)該有一個(gè)虛析構(gòu)函數(shù)

            l ???????? 如果一個(gè)類不是被設(shè)計(jì)為基類或者它們并不是按照多態(tài)的方式來使用的,不要為它們聲明虛析構(gòu)函數(shù)

            posted on 2005-11-10 16:43 nacci 閱讀(2177) 評(píng)論(3)  編輯 收藏 引用 所屬分類: C++漫談

            評(píng)論

            # re: 只在多態(tài)基類中聲明虛析構(gòu)函數(shù) 2005-11-24 12:18 sdfsd

            gfdsg  回復(fù)  更多評(píng)論   

            # re: 只在多態(tài)基類中聲明虛析構(gòu)函數(shù) 2006-02-28 10:11 zzq

            好貼
              回復(fù)  更多評(píng)論   

            # re: 只在多態(tài)基類中聲明虛析構(gòu)函數(shù) 2006-03-14 14:56 hhxz

            這個(gè)文章不錯(cuò),有意思  回復(fù)  更多評(píng)論   

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            導(dǎo)航

            統(tǒng)計(jì)

            常用鏈接

            留言簿(2)

            隨筆分類

            收藏夾

            大家的聲音

            積分與排名

            最新評(píng)論

            閱讀排行榜

            評(píng)論排行榜

            国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 韩国三级中文字幕hd久久精品 | 伊人久久大香线蕉影院95| 亚洲国产精品久久久久婷婷老年| 久久久综合香蕉尹人综合网| 亚洲精品无码久久久久去q| 丰满少妇高潮惨叫久久久| 精品综合久久久久久88小说| 无码日韩人妻精品久久蜜桃| 伊人久久大香线蕉影院95| 久久亚洲熟女cc98cm| 51久久夜色精品国产| 国产色综合久久无码有码| 狠狠人妻久久久久久综合| 久久久久免费看成人影片| 亚洲国产精品嫩草影院久久 | 日日狠狠久久偷偷色综合免费| 狼狼综合久久久久综合网| 欧美亚洲另类久久综合婷婷| 99国产精品久久| 亚洲精品乱码久久久久久蜜桃图片| 九九热久久免费视频| 久久久久四虎国产精品| 无遮挡粉嫩小泬久久久久久久| 亚洲午夜无码AV毛片久久| 精品久久久久一区二区三区| 国产精品一久久香蕉国产线看| 亚洲精品无码久久久久| 伊人久久成人成综合网222| 久久精品无码一区二区三区免费 | 亚洲国产精品无码久久久蜜芽| 青青草国产97免久久费观看| 国产精品美女久久久久av爽| 久久午夜电影网| 国产精品久久久久久| 国内精品久久久久影院免费| 久久精品aⅴ无码中文字字幕不卡| 中文字幕热久久久久久久| 少妇熟女久久综合网色欲| 国产aⅴ激情无码久久| 亚洲va久久久噜噜噜久久|