• <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>
            隨筆-341  評論-2670  文章-0  trackbacks-0

                復雜的東西寫多了,如今寫點簡單的好了。由于功能上的需要,Vczh Library++3.0被我搞得很離譜。為了開發(fā)維護的遍歷、減少粗心犯下的錯誤以及增強單元測試、回歸測試和測試工具,因此記錄下一些開發(fā)上的小技巧,以便拋磚引玉,造福他人。歡迎高手來噴,菜鳥膜拜。

                今天是關于內(nèi)存的最后一篇了。上一篇文章講了為什么不能對一個東西隨便memset。里面的demo代碼出了點小bug,不過我不喜歡在發(fā)文章的時候里面的demo代碼也拿去編譯和運行,所以大家有什么發(fā)現(xiàn)的問題就評論吧。這樣也便于后來的人不會受到誤導。這次說的仍然是構(gòu)造函數(shù)和析構(gòu)函數(shù)的事情,不過我們將通過親手開發(fā)一個智能指針的方法,知道引用計數(shù)如何幫助管理資源,以及錯誤使用引用計數(shù)的情況。

                首先先來看一下智能指針是如何幫助我們管理內(nèi)存的。現(xiàn)在智能指針的實現(xiàn)非常多,我就假設這個類型叫Ptr<T>吧。這跟Vczh Library++ 3.0所使用的實現(xiàn)一樣。

             1 class Base
             2 {
             3 public:
             4   virtual ~Base(){}
             5 };
             6 
             7 class Derived1 : public Base
             8 {
             9 };
            10 
            11 class Derived2 : public Base
            12 {
            13 };
            14 
            15 //---------------------------------------
            16 
            17 List<Ptr<Base>> objects;
            18 objects.Add(new Derived1);
            19 objects.Add(new Derived2);
            20 
            21 List<Ptr<Base>> objects2;
            22 objects2.Add(objects[0]);

                當然這里的List也是Vczh Library++3.0實現(xiàn)的,不過這玩意兒跟vector也好跟C#的List也好都是一個概念,因此也就不需要多加解釋了。我們可以看到智能指針的一個好處,只要沒有循環(huán)引用出現(xiàn),你無論怎么復制它,最終總是可以被析構(gòu)掉的。另一個例子告訴我們智能指針如何處理類型轉(zhuǎn)換:
            1 Ptr<Derived1> d1=new Derived1;
            2 Ptr<Base> b=d1;
            3 Ptr<Derived2> d2=b.Cast<Derived2>();
            4 // d2是空,因為b指向的是Derived1而不是Derived2。

                這就如同我們Derived1*可以隱式轉(zhuǎn)換到Base*,而當你使用dynamic_cast<Derived2*>(static_cast<Base*>(new Derived1))會得到0一樣。智能指針在幫助我們析構(gòu)對象的同時,也要做好類型轉(zhuǎn)換的工作。

                好了,現(xiàn)在先讓我們一步一步做出那個Ptr<T>。我們需要清楚這個智能指針所要實現(xiàn)的功能是什么,然后我們一個一個來做。首先讓我們列出一張表:
                1、沒有參數(shù)構(gòu)造的時候,初始化為空
                2、使用指針構(gòu)造的時候,擁有那個指針,并且在沒有任何智能指針指向那個指針的時候刪除掉該指針。
                3、智能指針進行復制的時候,兩個智能指針共同擁有該內(nèi)部指針。
                4、智能指針可以使用新的智能指針或裸指針重新賦值。
                5、需要支持隱式指針類型轉(zhuǎn)換,static_cast不支持而dynamic_cast支持的轉(zhuǎn)換則使用Cast<T2>()成員函數(shù)來解決。
                6、如果一個裸指針直接用來創(chuàng)建兩個智能指針的話,期望的情況是當兩個智能指針析構(gòu)掉的時候,該指針會被delete兩次從而崩潰。
                7、不處理循環(huán)引用。

                最后兩點實際上是錯誤使用智能指針的最常見的兩種情況。我們從1到5一個一個實現(xiàn)。首先是1。智能指針可以隱式轉(zhuǎn)換成bool,可以通過operator->()拿到內(nèi)部的T*。在沒有使用參數(shù)構(gòu)造的時候,需要轉(zhuǎn)換成false,以及拿到0:
             1 template<typename T>
             2 class Ptr
             3 {
             4 private:
             5   T* pointer;
             6   int* counter;
             7 
             8   void Increase()
             9   {
            10     if(counter)++*counter;
            11   }
            12 
            13   void Decrease()
            14   {
            15     if(counter && --*counter==0)
            16     {
            17       delete counter;
            18       delete pointer;
            19       counter=0;
            20       pointer=0;
            21     }
            22   }
            23 
            24 public:
            25   Ptr():pointer(0),counter(0)
            26   {
            27   }
            28 
            29   ~Ptr()
            30   {
            31     Decrease();
            32   }
            33 
            34   operator bool()const
            35   {
            36     return counter!=0;
            37   }
            38 
            39   T* operator->()const
            40   {
            41     return pointer;
            42   }
            43 };

                在這里我們實現(xiàn)了構(gòu)造函數(shù)和析構(gòu)函數(shù)。構(gòu)造函數(shù)把內(nèi)部指針和引用計數(shù)的指針都初始化為空,而析構(gòu)函數(shù)則進行引用計數(shù)的減一操作。另外兩個操作符重載很容易理解。我們主要來看看Increase函數(shù)和Decrease函數(shù)都分別做了什么。Increase函數(shù)在引用計數(shù)存在的情況下,把引用計數(shù)加一。而Decrease函數(shù)在引用計數(shù)存在的情況下,把引用計數(shù)減一,如果引用計數(shù)在減一過程中變成了0,則刪掉擁有的資源。

                當然到了這個時候智能指針還不能用,我們必須替他加上復制構(gòu)造函數(shù),operator=操作符重載以及使用指針賦值的情況。首先讓我們來看使用指針賦值的話我們應該加上什么:
             1   Ptr(T* p):pointer(0),counter(0)
             2   {
             3     *this=p;
             4   }
             5 
             6   Ptr<T>& operator=(T* p)
             7   {
             8     Decrease();
             9     if(p)
            10     {
            11       pointer=p;
            12       counter=new int(1);
            13     }
            14     else
            15     {
            16       pointer=0;
            17       counter=0;
            18     }
            19     return *this;
            20   }

                這里還是偷工減料了的,構(gòu)造函數(shù)接受了指針的話,還是轉(zhuǎn)給operator=去調(diào)用了。當一個智能指針被一個新指針賦值的時候,我們首先要減掉一個引用計數(shù),因為原來的指針再也不被這個智能指針共享了。之后就進行判斷,如果來的是0,那么就變成空。如果不是0,就擁有該指針,引用計數(shù)初始化成1。于是我們就可以這么使用了:
            1 Ptr<Base> b=new Derived1;
            2 Ptr<Derived2> d2=new Derived2;

                讓我們開始復制他們吧。復制的要領是,先把之前擁有的指針脫離掉,然后連接到一個新的智能指針上面去。我們知道非空智能指針有多少個,總的引用計數(shù)的和就是多少,只是分配到各個指針上面的數(shù)字不一樣而已:
             1   Ptr(const Ptr<T>& p):pointer(p.pointer),counter(p.counter)
             2   {
             3     Increase();
             4   }
             5 
             6   Ptr<T>& operator=(const Ptr<T>& p)
             7   {
             8     if(this!=&p)
             9     {
            10       Decrease();
            11       pointer=p.pointer;
            12       counter=p.counter;
            13       Increase();
            14     }
            15     return *this;
            16   }

                在上一篇文章有朋友指出重載operator=的時候需要考慮是不是自己賦值給自己,其實這是很正確的。我們寫每一類的時候,特別是當類擁有自己控制的資源的時候,需要非常注意這件事情。當然如果只是復制幾個對象而不會new啊delete還是close什么handle,那檢查不檢查也無所謂了。在這里我們非常清楚,當增加一個新的非空智能指針的時候,引用計數(shù)的總和會加一。當修改一個非空智能指針的結(jié)果也是非空的時候,引用計數(shù)的和保持不變。當然這是應該的,因為我們需要在所有非空智能指針都被毀掉的時候,釋放受保護的所有資源。

                到了這里一個智能指針基本上已經(jīng)能用了,但是還不能處理父類子類的情況。這個是比較麻煩的,一個Ptr<Derived>事實上沒有權限訪問Ptr<Base>的內(nèi)部對象。因此我們需要通過友元類來解決這個問題。現(xiàn)在讓我們來添加兩個新的函數(shù)吧,從一個任意的Ptr<C>復制到Ptr<T>,然后保證只有當C*可以隱式轉(zhuǎn)換成T*的時候編譯能夠通過:
             1   template<X> friend class Ptr;
             2 
             3   template<typename C>
             4   Ptr(const Ptr<C>& p):pointer(p.pointer),counter(p.counter)
             5   {
             6     Increase();
             7   }
             8 
             9   template<typename C>
            10   Ptr<T>& operator=(const Ptr<C>& p)
            11   {
            12     Decrease();
            13     pointer=p.pointer;
            14     counter=p.counter;
            15     Increase();
            16     return *this;
            17   }

                注意這里我們的operator=并不用檢查是不是自己給自己賦值,因為這是兩個不同的類,相同的話會調(diào)用上面那個operator=的。如果C*不能隱式轉(zhuǎn)換到T*的話,這里的pointer=p.pointer就會失敗,從而滿足了我們的要求。

                現(xiàn)在我們能夠做的事情就更多了:
            1 Ptr<Derived1> d1=new Derived1;
            2 Ptr<Base> b=d1;

                于是我們只剩下最后一個Cast函數(shù)了。這個函數(shù)內(nèi)部使用dynamic_cast來做判斷,如果轉(zhuǎn)換失敗,會返回空指針:
             1   tempalte<typename C>
             2   Ptr<C> Cast()const
             3   {
             4     C* converted=dynamic_cast<C*>(pointer);
             5     Ptr<C> result;
             6     if(converted)
             7     {
             8       result.pointer=converted;
             9       result.counter=counter;
            10       Increase();
            11     }
            12     return result;
            13   }

                這是一種hack的方法,平時是不鼓勵的……不過因為操作的都是Ptr,而且特化Ptr也是使用錯誤的一種,所以這里就不管了。我們會檢查dynamic_cast的結(jié)果,如果成功了,那么會返回一個非空的新智能指針,而且這個時候我們也要記住Increase一下。

                好了,基本功能就完成了。當然一個智能指針還要很多其他功能,譬如說比較什么的,這個就你們自己搞定哈。

                指針和內(nèi)存就說到這里了,下一篇講如何利用一個好的IDE構(gòu)造輕量級單元測試系統(tǒng)。我們都說好的工具能夠提高生產(chǎn)力,因此這種方法不能脫離一個好的IDE使用。
            posted on 2010-06-23 23:03 陳梓瀚(vczh) 閱讀(9858) 評論(15)  編輯 收藏 引用 所屬分類: C++實用技巧

            評論:
            # re: C++實用技巧(三) 2010-06-24 23:22 | zuhd
            全文可以理解為一段共享內(nèi)存的故事  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-25 05:31 | SonicLing
            效率存在問題。
            12 counter=new int(1);
            感覺這個new完全沒必要啊。

            而且Ptr<T>的值傳遞會很慢,完全沒有了指針的速度啊。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-25 05:41 | 陳梓瀚(vczh)
            @SonicLing
            我不會為了不是瓶頸部分的效率而讓開發(fā)效率大大下降的。加班的原因還不是因為程序?qū)懙寐?nbsp; 回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-25 05:42 | 陳梓瀚(vczh)
            @SonicLing
            這個new有必要,counter==1也是要有counter的。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-25 06:17 | SonicLing
            @陳梓瀚(vczh)
            我的意思是直接用int counter,而不是指針。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-25 17:48 | zuhd
            @SonicLing


            全文可以理解為一段共享內(nèi)存的故事

            你顯然沒有理解這段話的意思  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-25 20:02 | 陳梓瀚(vczh)
            @SonicLing
            那一共有三個Ptr,其中一個死了,你不可能同時改了另外兩個的counter。謝謝。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-26 07:58 | SonicLing
            @陳梓瀚(vczh)
            恩,我理解錯了。我寫過類似的指針,只不過把counter放在對象里,共享對象同時也共享了counter,這樣在傳遞和內(nèi)存分配方面效率高一點,成員只包含一個指針,優(yōu)化收益也高一些,免去了過多的new/delete。因為跟你一樣,也在寫編譯/解釋器,所以非常看重效率。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-26 08:10 | 陳梓瀚(vczh)
            @SonicLing
            T顯然是任意的,你不能修改。所以這才是我分出來的原因。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-26 08:10 | 陳梓瀚(vczh)
            @SonicLing
            虛擬機效率高就好了,編譯器沒必要。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-26 17:52 | 飯中淹
            為什么不把pointer和counter放在一個結(jié)構(gòu)里,new這個結(jié)構(gòu)?
            我做這個東西的時候,就是做這樣一個core_struct,然后加一個core_struct池來維護內(nèi)存。所有的Ptr傳遞和保存的就是這個core_struct的指針。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-06-26 21:02 | 陳梓瀚(vczh)
            @飯中淹
            你這種做法在遇到類型轉(zhuǎn)換的時候就有問題了。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-07-01 00:32 |
            使用type_traits模板技術可以非常漂亮的解決智能指針的子父類轉(zhuǎn)換問題,如果要安全轉(zhuǎn)換子父類指針,根本辦法是打開RTTI,在智能指針實現(xiàn)的內(nèi)部使用dynamic_cast,并在出錯時拋出異常。
            但是RTTI我只建議在個別模塊中使用,這樣不會影響別的模塊的對象內(nèi)存結(jié)構(gòu),這種結(jié)構(gòu)會占用多余的內(nèi)存。  回復  更多評論
              
            # re: C++實用技巧(三) 2010-07-01 01:06 | 陳梓瀚(vczh)
            @釀
            我這不就這么做嗎,請認真閱讀。話說回來,RTTI在不是瓶頸的時候,我絕對不關掉它。  回復  更多評論
              
            # re: C++實用技巧(三) 2014-10-30 00:15 | 大花貓
            Ptr<T>& operator=(T* p)這個函數(shù)怎么沒有檢查自賦值  回復  更多評論
              
            久久综合久久美利坚合众国| 国产精品久久久久久久久久影院| 久久综合五月丁香久久激情| 久久精品成人一区二区三区| 久久久久久亚洲精品不卡| 国产高潮国产高潮久久久91| 理论片午午伦夜理片久久 | 91精品国产91久久久久久蜜臀| 999久久久国产精品| 少妇被又大又粗又爽毛片久久黑人 | 日产精品久久久一区二区| 久久精品国产亚洲77777| 色播久久人人爽人人爽人人片aV| 久久久久久久免费视频| 亚洲国产精品婷婷久久| 亚洲国产成人久久综合区| 嫩草影院久久国产精品| 伊人热热久久原色播放www| 欧美一区二区三区久久综| 久久99精品国产99久久6| 久久综合九色综合精品| 久久只有这里有精品4| 国产综合成人久久大片91| 亚洲va久久久噜噜噜久久狠狠| 久久99这里只有精品国产| 欧美亚洲国产精品久久蜜芽 | 亚洲人成伊人成综合网久久久| 久久免费大片| 亚洲一区二区三区日本久久九| 无码国内精品久久人妻蜜桃| 国内精品伊人久久久影院| 超级碰久久免费公开视频| 国产亚洲欧美精品久久久| 嫩草影院久久国产精品| 麻豆成人久久精品二区三区免费 | 久久国产一片免费观看| 人人狠狠综合久久亚洲88| 国产精品一久久香蕉产线看| 精品人妻伦九区久久AAA片69| 国产精品一久久香蕉国产线看观看| 久久精品国产免费观看|