C++实用技巧(三)
生活随笔
收集整理的這篇文章主要介紹了
C++实用技巧(三)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
復雜的東西寫多了,如今寫點簡單的好了。由于功能上的需要,Vczh Library++3.0被我搞得很離譜。為了開發維護的遍歷、減少粗心犯下的錯誤以及增強單元測試、回歸測試和測試工具,因此記錄下一些開發上的小技巧,以便拋磚引玉,造福他人。歡迎高手來噴,菜鳥膜拜。
??? 今天是關于內存的最后一篇了。上一篇文章講了為什么不能對一個東西隨便memset。里面的demo代碼出了點小bug,不過我不喜歡在發文章的時候里面的demo代碼也拿去編譯和運行,所以大家有什么發現的問題就評論吧。這樣也便于后來的人不會受到誤導。這次說的仍然是構造函數和析構函數的事情,不過我們將通過親手開發一個智能指針的方法,知道引用計數如何幫助管理資源,以及錯誤使用引用計數的情況。
??? 首先先來看一下智能指針是如何幫助我們管理內存的。現在智能指針的實現非常多,我就假設這個類型叫Ptr<T>吧。這跟Vczh Library++ 3.0所使用的實現一樣。
?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實現的,不過這玩意兒跟vector也好跟C#的List也好都是一個概念,因此也就不需要多加解釋了。我們可以看到智能指針的一個好處,只要沒有循環引用出現,你無論怎么復制它,最終總是可以被析構掉的。另一個例子告訴我們智能指針如何處理類型轉換:
1?Ptr<Derived1>?d1=new?Derived1;
2?Ptr<Base>?b=d1;
3?Ptr<Derived2>?d2=b.Cast<Derived2>();
4?//?d2是空,因為b指向的是Derived1而不是Derived2。
??? 這就如同我們Derived1*可以隱式轉換到Base*,而當你使用dynamic_cast<Derived2*>(static_cast<Base*>(new Derived1))會得到0一樣。智能指針在幫助我們析構對象的同時,也要做好類型轉換的工作。
??? 好了,現在先讓我們一步一步做出那個Ptr<T>。我們需要清楚這個智能指針所要實現的功能是什么,然后我們一個一個來做。首先讓我們列出一張表:
??? 1、沒有參數構造的時候,初始化為空
??? 2、使用指針構造的時候,擁有那個指針,并且在沒有任何智能指針指向那個指針的時候刪除掉該指針。
??? 3、智能指針進行復制的時候,兩個智能指針共同擁有該內部指針。
??? 4、智能指針可以使用新的智能指針或裸指針重新賦值。
??? 5、需要支持隱式指針類型轉換,static_cast不支持而dynamic_cast支持的轉換則使用Cast<T2>()成員函數來解決。
??? 6、如果一個裸指針直接用來創建兩個智能指針的話,期望的情況是當兩個智能指針析構掉的時候,該指針會被delete兩次從而崩潰。
??? 7、不處理循環引用。
??? 最后兩點實際上是錯誤使用智能指針的最常見的兩種情況。我們從1到5一個一個實現。首先是1。智能指針可以隱式轉換成bool,可以通過operator->()拿到內部的T*。在沒有使用參數構造的時候,需要轉換成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?};
??? 在這里我們實現了構造函數和析構函數。構造函數把內部指針和引用計數的指針都初始化為空,而析構函數則進行引用計數的減一操作。另外兩個操作符重載很容易理解。我們主要來看看Increase函數和Decrease函數都分別做了什么。Increase函數在引用計數存在的情況下,把引用計數加一。而Decrease函數在引用計數存在的情況下,把引用計數減一,如果引用計數在減一過程中變成了0,則刪掉擁有的資源。
??? 當然到了這個時候智能指針還不能用,我們必須替他加上復制構造函數,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???}
??? 這里還是偷工減料了的,構造函數接受了指針的話,還是轉給operator=去調用了。當一個智能指針被一個新指針賦值的時候,我們首先要減掉一個引用計數,因為原來的指針再也不被這個智能指針共享了。之后就進行判斷,如果來的是0,那么就變成空。如果不是0,就擁有該指針,引用計數初始化成1。于是我們就可以這么使用了:
1?Ptr<Base>?b=new?Derived1;
2?Ptr<Derived2>?d2=new?Derived2;
??? 讓我們開始復制他們吧。復制的要領是,先把之前擁有的指針脫離掉,然后連接到一個新的智能指針上面去。我們知道非空智能指針有多少個,總的引用計數的和就是多少,只是分配到各個指針上面的數字不一樣而已:
?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,那檢查不檢查也無所謂了。在這里我們非常清楚,當增加一個新的非空智能指針的時候,引用計數的總和會加一。當修改一個非空智能指針的結果也是非空的時候,引用計數的和保持不變。當然這是應該的,因為我們需要在所有非空智能指針都被毀掉的時候,釋放受保護的所有資源。
??? 到了這里一個智能指針基本上已經能用了,但是還不能處理父類子類的情況。這個是比較麻煩的,一個Ptr<Derived>事實上沒有權限訪問Ptr<Base>的內部對象。因此我們需要通過友元類來解決這個問題。現在讓我們來添加兩個新的函數吧,從一個任意的Ptr<C>復制到Ptr<T>,然后保證只有當C*可以隱式轉換成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=并不用檢查是不是自己給自己賦值,因為這是兩個不同的類,相同的話會調用上面那個operator=的。如果C*不能隱式轉換到T*的話,這里的pointer=p.pointer就會失敗,從而滿足了我們的要求。
??? 現在我們能夠做的事情就更多了:
1?Ptr<Derived1>?d1=new?Derived1;
2?Ptr<Base>?b=d1;
??? 于是我們只剩下最后一個Cast函數了。這個函數內部使用dynamic_cast來做判斷,如果轉換失敗,會返回空指針:
?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的結果,如果成功了,那么會返回一個非空的新智能指針,而且這個時候我們也要記住Increase一下。
??? 好了,基本功能就完成了。當然一個智能指針還要很多其他功能,譬如說比較什么的,這個就你們自己搞定哈。
??? 指針和內存就說到這里了,下一篇講如何利用一個好的IDE構造輕量級單元測試系統。我們都說好的工具能夠提高生產力,因此這種方法不能脫離一個好的IDE使用。
from:?http://www.cppblog.com/vczh/archive/2010/06/24/118635.html
總結
以上是生活随笔為你收集整理的C++实用技巧(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++实用技巧(二)
- 下一篇: C++实用技巧(四)