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

            清風(fēng)竹林

            ぷ雪飄絳梅映殘紅
               ぷ花舞霜飛映蒼松
                 ----- Do more,suffer less

            C++通用刪除器設(shè)計(jì)

            C++通用刪除器設(shè)計(jì)

            版本:0.1

            最后修改:2010-11-15

            撰寫:李現(xiàn)民


            概述

            很久以前,我寫過一篇短文討論如何在C++項(xiàng)目中避免使用delete的設(shè)想,基本方法是使用域(scope)對(duì)象或std::auto_ptr代替。盡管當(dāng)時(shí)已經(jīng)討論在所有可能的情況,但后面在實(shí)際項(xiàng)目實(shí)施中發(fā)現(xiàn)效果并不好。原因是方面的,比如在使用std::auto_ptr時(shí)會(huì)存在以下不得因素:

            1. 可能的額外開銷外(其實(shí)很小);

            2. 你需要時(shí)刻小心對(duì)象所有權(quán)的問題。盡管可能只需要稍微注意一下就可以了,但似乎沒有任何程序員喜歡過提心吊膽的日子;

            3. 你不能在容器(比如std::vector)中存儲(chǔ)std::auto_ptr對(duì)象;


            基于以上原因,類似于Text* pText = new Text;這種直接在堆上申請(qǐng)內(nèi)存的方式還是在代碼得到了大量應(yīng)用。而接下來就是如何安全、有效的回收這些內(nèi)存的問題,這也正是本文所討論的話題。

            回收單個(gè)堆對(duì)象

            // delete a object pointer and reset it
            template<class T> void delete_null(T*& p)
            {
            // check if T is incomplete type, if it is, the compiler will report an error
            typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
            (void) sizeof(type_must_be_complete);
            // delete the pointer and reset it
            delete p;
            p =NULL;
            }


            這是一個(gè)模板函數(shù),它主要有三個(gè)作用:

            第一個(gè)作用是檢查被刪除對(duì)象的類型完整性。這通常無法引起人們的重視,但在某些情況下可能會(huì)導(dǎo)致未定義行為,比如以下代碼:

            Text* pText = new Text;
            void* pData = pText;
            delete pData;

            Text*類對(duì)象pText 被轉(zhuǎn)換成了擁有void*對(duì)象pData,并對(duì)pData 調(diào)用了delete 刪除操作。在這種情況下編譯器的行為是未知的,但至少有一點(diǎn):由于編譯器無法推導(dǎo)pData 的原始類型,因此無法調(diào)用對(duì)象的析構(gòu)函數(shù)。

            // check if T is incomplete type, if it is, the compiler will report an error
            typedef char type_must_be_complete[ sizeof(T)? 1: -1 ];
            (void) sizeof(type_must_be_complete);

            這兩句代碼可以檢查被刪除對(duì)象的類型完整性。其效果發(fā)生在編譯期,如果對(duì)類型不完整的對(duì)象調(diào)用delete_null 刪除操作,將引起編譯錯(cuò)誤。它沒有運(yùn)行期開銷,因此使用delete_null 帶來的安全性實(shí)際上免費(fèi)的。

            更加詳細(xì)的解釋可以參考boost庫中的checked_delete.hpp

            delete_null 的第二個(gè)作用是回收堆對(duì)象,這沒有什么可說的。

            delete_null 的第三個(gè)作用是將對(duì)象指針設(shè)置為NULL這主要是為了應(yīng)對(duì)指針有效性檢查,屬于常規(guī)手段。

            另外,注意到delete_null 被設(shè)計(jì)為一個(gè)模板函數(shù),在發(fā)布版本(Release)中,它將以內(nèi)聯(lián)代碼(inline)的形式存在,因此不會(huì)有運(yùn)用期函數(shù)調(diào)用開銷。


            回收容器中的堆對(duì)象


            // delete container (std::vector, std::list) items and reset them to NULL
            template< typename InputIterator > void delete_null(InputIterator first, InputIterator last)
            {
            while(last != first)
            {
            delete_null(*first);
            ++first;
            }
            }

            // delete functor, used for iterative delete
            struct deleter
            {
            template< typename T > void operator()(T*& p)
            {
            delete_null(p);
            }
            };


            這段代碼分為兩部分:一個(gè)同樣叫delete_null 的模板函數(shù)與一個(gè)名為deleter的仿函數(shù)。

            先來看第一部分,它同樣叫delete_null,與前面介紹的那個(gè)版本所不同的是它接受一對(duì)迭代器作用輸入條件,其作用是回收[first, last) 范圍內(nèi)所有堆對(duì)象。與std::for_each等很多STL標(biāo)準(zhǔn)算法類似,該函數(shù)可以同時(shí)應(yīng)用于普通數(shù)組或存儲(chǔ)單值的標(biāo)準(zhǔn)容器(包括std::vector, std::list, std::set等,不包含std::map)。


            第二部分比較有意思:它是一個(gè)仿函數(shù)。它可以在一定程度上代替delete_null(first, last),以下代碼展示了分別使用這兩種方式回收容器中的堆對(duì)象的方法

            typedef std::vector<Text*> TextPack;
            TextPack uTexts1, uTexts2;
            const int datasize = 100;
            for (int i= 0; i< datasize; ++i)
            {
            uTexts1.push_back(new Text);
            uTexts2.push_back(new Text);
            }

            //
            使用delete_null
            delete_null(uTexts1.begin(), uTexts1.end());
            // 使用deleter
            std::for_each(uTexts2.begin(), uTexts2.end(), deleter());


            可以看到前者稍微簡(jiǎn)潔一些(包括最終的匯編代碼),那么問題來了:為什么還需要代碼量更大一些的deleter 仿函數(shù)?

            理由是:并不是所有存儲(chǔ)堆對(duì)象的集合都是直接存儲(chǔ)對(duì)象指針的。比如可以將指針存儲(chǔ)在std::map中“值”部分,甚至有些自定義集合只提供了遍歷函數(shù)(類似于std::for_each),但并不公開迭代器接口。在這些情況下,我們就可以使用deleter 仿函數(shù)進(jìn)行堆對(duì)象回收。



            posted on 2010-11-15 14:46 李現(xiàn)民 閱讀(3249) 評(píng)論(16)  編輯 收藏 引用 所屬分類: design

            評(píng)論

            # re: C++通用刪除器設(shè)計(jì) 2010-11-15 22:11 ,。。

            自作聰明,無語。。。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì)[未登錄] 2010-11-15 23:09 Jeff

            博主,有問題請(qǐng)教:
            1、為什么不直接使用boost中提供的smart ptr呢?
            2、你的實(shí)現(xiàn)是否是“異常安全”呢?  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-16 09:18 李現(xiàn)民

            @Jeff
            唉, 這批評(píng)還真犀利呀!
            1. 不直接使用boost中的智能指針最直接的原因是boost不穩(wěn)定,而且并不是boost中的所有特性都對(duì)項(xiàng)目有幫助的。你可能會(huì)問為什么我不自己實(shí)現(xiàn)一個(gè),一個(gè)類似于shared_ptr的東西,除了那一點(diǎn)性能損失外,真正的原因是我還沒有發(fā)現(xiàn)讓我不得不用的理由。而且,項(xiàng)目中有很多地方是不能直接調(diào)用析構(gòu)函數(shù)的,而是需要單獨(dú)寫一個(gè)Destroy的函數(shù),因?yàn)闀r(shí)機(jī)。
            2. 關(guān)于異常安全,我想,如果析構(gòu)函數(shù)本身是異常安全的話,那么我的代碼本身應(yīng)該算異常安全了吧?反之,如果析構(gòu)函數(shù)本身不安全的話,那無論采用什么方法析構(gòu)都是有問題的。

            如果我的想法有什么問題的話,請(qǐng)不吝指正,非常感謝。
              回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-16 10:52 空明流轉(zhuǎn)

            @李現(xiàn)民

            扯淡,有什么不穩(wěn)定的。

            shared_ptr是一個(gè)完全OK的選擇。至于性能損失,我還真沒遇到過。一個(gè)atomic的add,能有多少性能損耗?能和你的res alloc和free比么?

            還有,RAII本身就是一種Idiom。你覺得不適用,只是因?yàn)槟銓?duì)RAII這個(gè)Idiom本身不熟悉而已。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-16 11:16 李現(xiàn)民

            @空明流轉(zhuǎn)
            我所指的“不穩(wěn)定”是指boost一直在開發(fā),里面有長(zhǎng)期積累下來的庫,也有新加入的庫,只是加入到項(xiàng)目中,新庫在未證明其穩(wěn)定性之前也有可能被使用,而這可能導(dǎo)致一些問題。

            另外, 你說得對(duì),相比于資源的分配與回收,shared_ptr的開銷是可以忽略的,但我們的系統(tǒng)中現(xiàn)在還沒有這樣的機(jī)制,而且我說過了,真正的原因是我尚未發(fā)現(xiàn)讓我不得不用它的理由。

            最后, 我其實(shí)是很贊同使用RAII的,實(shí)際上,我們已經(jīng)在陸續(xù)使用它了。

            感謝回復(fù)!  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-16 13:40 陳梓瀚(vczh)

            @李現(xiàn)民
            那你就自己寫一個(gè)shared_ptr吧,最多發(fā)現(xiàn)不穩(wěn)定的時(shí)候你還可以改嘛,改著改著就穩(wěn)定了。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-16 13:41 陳梓瀚(vczh)

            @李現(xiàn)民
            不得不用的理由有很多的,譬如說我最推崇的一條就是:“不使用它會(huì)浪費(fèi)人類的時(shí)間”  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì)[未登錄] 2010-11-16 22:37 Jeff

            @李現(xiàn)民
            恐怕你誤解我的意思了。我并沒有批評(píng)的意思,只是想搞清楚一些問題。
            關(guān)于boost的“不穩(wěn)定”,在實(shí)際項(xiàng)目的運(yùn)用時(shí),只選擇使用某一個(gè)版本的boost使用是就好了。對(duì)一個(gè)項(xiàng)目而言,boost的版本是穩(wěn)定的。這樣能避免一些配置管理的問題。
            另外,使用boost的一個(gè)原因就是避免重復(fù)造輪子:)當(dāng)然,一定要選擇boost中質(zhì)量高、穩(wěn)定性好的功能才能在項(xiàng)目中使用?!叭∑渚A、去其糟粕”,呵呵……
              回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-17 10:36 李現(xiàn)民

            @陳梓瀚(vczh)
            看了這么多回復(fù), 如果我沒想錯(cuò)的話,是不是大家都推崇使用shared_ptr替代原始對(duì)象指針?如果多數(shù)人都這認(rèn)為的話,那肯定是有道理的。如你所言,看來有時(shí)間我得去仔細(xì)研究一下shared_ptr了。

            我現(xiàn)在能想到的問題是,假如我自己寫了一個(gè)shared_ptr加入到了舊的項(xiàng)目中,那么所有其它使用該對(duì)象的地方(包括參數(shù))是否都必須帶著shared_ptr<T>的聲明呢?

            感謝回復(fù),不忙的時(shí)候研究一下。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-17 10:40 李現(xiàn)民

            @Jeff
            沒有, 非常歡迎你的評(píng)論。大家的回復(fù)讓我想了很多,這些都是非常意外而重要的收獲。

            其實(shí), 最直接的問題是:我沒有權(quán)利在項(xiàng)目中引入一個(gè)像boost 這樣大的庫,因此這不但限制了應(yīng)用,也限制了我的眼界。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-17 17:34 冬瓜

            這個(gè)和boost::shared_ptr沒關(guān)系吧~~~~,
            我看了一下,它的做用是對(duì)刪除void *的指針,會(huì)產(chǎn)生一個(gè)編譯器的錯(cuò)誤。以避免一些人員將T *指針轉(zhuǎn)換成void *,再刪除,而造成沒有執(zhí)行析構(gòu)函數(shù)的問題。不過,這種情況非常少。

            shared_ptr的作用是0引用時(shí)刪除。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-17 17:58 李現(xiàn)民

            @冬瓜
            是這樣的, 大家覺得如果直接用shared_ptr的話就沒有必要寫一個(gè)delete_null函數(shù)了
              回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-17 18:24 星綻紫輝

            沒有絕對(duì)的,指針引用計(jì)數(shù)用在比較復(fù)雜的環(huán)境(大塊的資源使用、隊(duì)列資源)中非常有用(比如內(nèi)存池的分配和釋放),你可以寫自己的AddRef和DelRef函數(shù)來管理資源, 但是通常簡(jiǎn)單類就沒必要了,如果連簡(jiǎn)單的實(shí)現(xiàn)類、模式類、UI類都這么搞,你會(huì)暈的。。。  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-17 19:42 李現(xiàn)民

            @星綻紫輝
            呵呵, 受教了  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-18 10:09 冬瓜

            @李現(xiàn)民
            你的delete_null和shared_ptr是兩個(gè)完全不同的東西。
            作用目標(biāo)都不一樣~  回復(fù)  更多評(píng)論   

            # re: C++通用刪除器設(shè)計(jì) 2010-11-18 10:32 陳梓瀚(vczh)

            @李現(xiàn)民
            shared_ptr只是一個(gè)例子哈,當(dāng)然也可以用來刪除東西,因?yàn)樗С值霓D(zhuǎn)換比起C++的指針轉(zhuǎn)換還是更嚴(yán)格的。

            我自己也實(shí)現(xiàn)了一次,在ptr<A>和ptr<B>進(jìn)行轉(zhuǎn)換用的是dynamic_cast,這樣如果一個(gè)類型沒有虛函數(shù)就會(huì)報(bào)錯(cuò),因此我不會(huì)因?yàn)殄e(cuò)誤轉(zhuǎn)換了指針而導(dǎo)致析構(gòu)的時(shí)候發(fā)生問題,就非常安全了。  回復(fù)  更多評(píng)論   

            少妇久久久久久被弄到高潮| 伊人久久大香线蕉综合网站| 综合久久国产九一剧情麻豆| 久久精品国产精品亚洲艾草网美妙 | 久久99精品久久久久久齐齐| 国产婷婷成人久久Av免费高清| 成人午夜精品无码区久久| 99久久香蕉国产线看观香| 亚洲欧美日韩精品久久亚洲区| 国产精品久久久天天影视香蕉| 久久精品国内一区二区三区| 久久国产精品-久久精品| 久久久精品免费国产四虎| 99久久精品免费看国产免费| 9191精品国产免费久久| 国产精自产拍久久久久久蜜| 久久精品国产72国产精福利| 久久人人超碰精品CAOPOREN| 中文字幕无码久久久| 狠狠色丁香久久婷婷综合| 日日噜噜夜夜狠狠久久丁香五月| 久久久久99精品成人片试看| 国产精品久久波多野结衣| 99久久精品免费观看国产| 日日狠狠久久偷偷色综合0| 久久久久亚洲av成人网人人软件| 日韩精品久久无码人妻中文字幕| 久久精品人人槡人妻人人玩AV | 国产亚洲精久久久久久无码| 久久久久久久久无码精品亚洲日韩| 精品亚洲综合久久中文字幕| 国产精品成人久久久久三级午夜电影 | 久久久久久久综合综合狠狠| 少妇人妻综合久久中文字幕| 久久人人妻人人爽人人爽| 国产巨作麻豆欧美亚洲综合久久 | 69久久夜色精品国产69| 久久成人18免费网站| 精品国产99久久久久久麻豆 | 久久精品国产国产精品四凭 | 久久99精品综合国产首页|