C++ — 智能指針的簡單實現以及循環引用問題

http://blog.csdn.net/dawn_sf/article/details/70168930

智能指針

____________________________________________________



今天我們來看一個高大上的東西,它叫智能指針。 哇這個名字聽起來都智能的不得了,其實等你了解它你一定會有一點失望的。。。。因為它說白了

就是個管理資源的。智能指針的原理就是管理資源的RALL機制,我們先來簡單了解一下

RALL機制:RALL機制便是通過利用對象的自動銷毀,使得資源也具有了生命周期,有了自動銷毀(自動回收)的功能。RAII全稱為Resource?

Acquisition Is Initialization,它是在一些面向對象語言中的一種慣用法。RAII源于C++,在Java,C#,D,Ada,Vala和Rust中也有應用。資源分配

即初始化,定義一個類來封裝資源的分配和釋放,在構造函數完成資源的分配和初始化,在析構函數完成資源的清理,可以保證資源的正確初始化和釋

放。RAII要求,資源的有效期與持有資源的對象的生命期嚴格綁定,即由對象的構造函數完成資源的分配(獲取),同時由析構函數完成資源的釋放。在

這種要求下,只要對象能正確地析構,就不會出現資源泄露問題。RALL在這里就是簡單提一下而已,現在我們來看我們今天的主角智能指針。

?

智能指針(smart pointer)是存儲指向動態分配(堆)對象指針的類。它的誕生理由就是,為粗心和懶的設計的,但是這個設計一定不是反人類

的,因為無論你有多厲害只要你是人你總會有犯錯誤的時候,所以智能指針可以很好地幫助我們,程序員每次?new?出來的內存都要手動?delete。程序

員忘記?delete,流程太復雜,最終導致沒有?delete

異常導致程序過早退出,沒有執行?delete?的情況并不罕見。其實智能指針只是怕你忘了delete,而專門設置出來的個對象。有沒有感覺它頓時不夠

智能呢,但是你絕對不能否認它的實用性和重要性。現在我們來看看智能指針的使用吧:


對于編譯器來說,智能指針實際上是一個棧對象,并非指針類型,在棧對象生命期即將結束時,智能指針通過析構函數釋放有它管理的堆內存。所有智

能指針都重載了“operator->”操作符,直接返回對象的引用,用以操作對象。訪問智能指針原來的方法則使用“.”操作符。先拋開智能指針的幾個

版本不說,我們先來講一下它里面的 * 和 -> 是何進行運算符重載的。下面是我定義的一個類,他只是為了實現原生指針的 * 和 -> 功能:


  1. struct?AA??
  2. {??
  3. ????int?a?=?10;??
  4. ????int?b?=?20;??
  5. };??
  6. template<class?T>??
  7. class?A??
  8. {??
  9. public:??
  10. ??
  11. ????A(T*?ptr)??
  12. ????????:_ptr(ptr)??
  13. ????{}??
  14. ??
  15. ????T*?operator->()??
  16. ????{??
  17. ????????return?_ptr;??
  18. ????}??
  19. ??
  20. ????T&?operator*()??
  21. ????{??
  22. ????????return?*_ptr;??
  23. ????}??
  24. ??
  25. ????A(A<T>&?ap)??
  26. ????{}??
  27. ????A<T>&?operator=(A<T>&?ap)??
  28. ????{}??
  29. ????~A()??
  30. ????{delete?_ptr;}??
  31. protected:??
  32. ????T*?_ptr;??
  33. };??
  34. ??
  35. int?main()??
  36. {??
  37. ????A<int>ap1(new?int);??
  38. ????*ap1?=?10;??
  39. ????A<AA>ap2(new?AA);??
  40. ????cout?<<?*ap1?<<?endl;??
  41. ????cout?<<?(ap2->a)<<"??"<<(ap2->b)?<<?endl;??
  42. ????return?0;??
  43. }??


請忽略這個粗糙的A類和AA結構體,我們的目的只是實現原生函數的功能,那么我的功能實現了嗎?

? ? ? ? ? ?

這里結果沒有一點問題,那么我們現在的注意點就應該放在這里是如何實現的:









智能指針的三大版本的實現==>



好了前面那些磨人的小妖精終于清理完了,現在我們真真正正的進入主題,智能指針的發展史以及它的常見的三個版本。

? ? ? ? ? ? ? ? ? ? ? ?1.管理權轉移 ? 2.簡單粗暴的防拷貝 ?3.引用計數版本

注意這里我只是實現簡單的思想,可能寫的不是很好,望大家指出幫助我改正錯誤。

管理權轉移==>


這個智能指針是1998應用到VS上的,現在我們來實現第一個,何為管理權轉移呢?


現在我列出該思想的實現代碼:

  1. template<class?T>??
  2. class?AutoPtr??
  3. {??
  4. public:??
  5. ??
  6. ????AutoPtr(T*?ptr)??
  7. ????????:_ptr(ptr)??
  8. ????{}??
  9. ??
  10. ????T*?operator->()??
  11. ????{??
  12. ????????return?_ptr;??
  13. ????}??
  14. ??
  15. ????T&?operator*()??
  16. ????{??
  17. ????????return?*_ptr;??
  18. ????}??
  19. ??
  20. ????AutoPtr(AutoPtr<T>&?ap)??
  21. ????{??
  22. ????????this->_ptr?=?ap._ptr;??
  23. ????????ap._ptr?=?NULL;??
  24. ????}??
  25. ????AutoPtr<T>&?operator=(AutoPtr<T>&?ap)??
  26. ????{??
  27. ????????if?(this?!=?&ap)??
  28. ????????{??
  29. ????????????delete?this->_ptr;??
  30. ????????????this->_ptr?=?ap._ptr;??
  31. ????????????ap._ptr?=?NULL;??
  32. ????????}??
  33. ????????return?*this;??
  34. ????}??
  35. ????~AutoPtr()??
  36. ????{??
  37. ????????cout?<<?"智能指針爸爸已經釋放過空間了"?<<?endl;??
  38. ????????delete?_ptr;??
  39. ????}??
  40. protected:??
  41. ????T*?_ptr;??
  42. };??
  43. ??
  44. int?main()??
  45. {??
  46. ????AutoPtr<int>ap1(new?int);??
  47. ????*ap1?=?10;??
  48. ????AutoPtr<int>ap2(ap1);??
  49. ????AutoPtr<int>ap3(ap2);??
  50. ????*ap3?=?20;??
  51. ????ap2?=?ap3;??
  52. ????cout?<<?*ap2?<<endl;??
  53. ??
  54. ????return?0;??
  55. }??

現在我們先看看它使用普通操作時的結果如何:




現在的結果真的太符合我們的預料了,我們要的就是這樣的結果,當你還沉浸自己成功的喜悅的時候,這里雖然成功實現了自動釋放空間的功能還有指

針的功能,但是看看下面這種情況:我們把main函數內修改成這個樣子:

int main()
?{
AutoPtr<int>ap1(new int);
*ap1 = 10;
AutoPtr<int>ap2(ap1);
cout << *ap1 << endl;
return 0;
?}






然后結果。。調試到這一步程序崩潰了,罪魁禍首就是AutoPtr<int>ap2(ap1),這里原因就是ap2完全的奪取了ap1的管理權。然后導致ap1無家可歸,

訪問它的時候程序就會崩潰。如果在這里調用ap2 = ap1程序一樣會崩潰原因還是ap1被徹徹底底的奪走一切,所以這種編程思想及其不符合C++思想,

所以它的設計思想就是有一定的缺陷。所以一般不推薦使用Autoptr智能指針。 使用了也絕對不能使用"="和拷貝構造。歷史在發展,所以我們見到接

下來這種想法:??


簡單粗暴法(防拷貝)==>


scoped智能指針?屬于?boost?庫,定義在?namespace boost?中,包含頭文件#include<boost/smart_ptr.hpp>?便可以使用。scoped智能指

??AutoPtr智能指針?一樣,可以方便的管理單個堆內存對象,特別的是,scoped智能指針?享所有權,避免了?AutoPtr智能指針惱人的幾個問

題,它直接就告訴用戶我不提供"="和拷貝構造這兩個功能,你別用,用了我也讓你編不過去。來看它的實現:

  1. template<class?T>??
  2. class?ScopedPtr??
  3. {??
  4. public:??
  5. ????ScopedPtr()??
  6. ????{}??
  7. ????AutoPtr(T*?ptr)??
  8. ????????:_ptr(ptr)??
  9. ????{}??
  10. ??
  11. ????T*?operator->()??
  12. ????{??
  13. ????????return?_ptr;??
  14. ????}??
  15. ??
  16. ????T&?operator*()??
  17. ????{??
  18. ????????return?*_ptr;??
  19. ????}??
  20. ????~AutoPtr()??
  21. ????{??
  22. ????????cout?<<?"智能指針爸爸已經釋放過空間了"?<<?endl;??
  23. ????????delete?_ptr;??
  24. ????}??
  25. ??
  26. protected:??
  27. ????ScopedPtr(ScopedPtr<T>&?s);??
  28. ????ScopedPtr<T>?operator=(ScopedPtr<T>&?s);??
  29. protected:??
  30. ????T*?_ptr;??
  31. };??

它的意思就是,我根本不會提供拷貝構造 和 "="的功能,他強任他強,我就是這樣。他確實解決上一個智能指針的問題,他直接讓用戶不能使用這個

功能,這個思想確實有點反人類。。由于scoped智能指針獨享所有權,當我們真真需要復制智能指針時,需求便滿足不了了,如此我們再引入一個智能

指針,專門用于處理復制,參數傳遞的情況。這便是如下的shared智能指針。


引用計數版本==>


接下來我們看最后一種,也就是我們現在經常用到的shared智能指針,等到智能指針發展到這一步也就很成熟了它已經幾乎完美的解決所有功能,因為

它使用了引用計數版本當指向該片資源的*_num變成0的時候,釋放該資源.


  1. template<class?T>??
  2. class?shared??
  3. {??
  4. public:??
  5. ????shared(T*?ptr)??
  6. ????????:_ptr(ptr)??
  7. ????????,?_num(new?int(1))??
  8. ????{??
  9. ????}??
  10. ????shared(const?shared<T>&?ap)??
  11. ????????:_ptr(ap._ptr)??
  12. ????????,?_num(ap._num)??
  13. ????{??
  14. ????????++(*_num);??
  15. ????}??
  16. ????shared<T>&?operator=(const?shared<T>&?ap)??
  17. ????{??
  18. ????????if?(_ptr?!=?ap._ptr)??
  19. ????????{??
  20. ????????????Release();??
  21. ????????????_ptr?=?ap._ptr;??
  22. ????????????_num?=?ap._num;??
  23. ????????????++(*_num);??
  24. ????????}??
  25. ????????return?*this;??
  26. ????}??
  27. ????T*?operator->()??
  28. ????{??
  29. ????????return?_ptr;??
  30. ????}??
  31. ??????
  32. ????T&?operator*()??
  33. ????{??
  34. ????????return?*_ptr;??
  35. ????}??
  36. ????void?Release()??
  37. ????{??
  38. ????????if?(0?==?(--*_num))??
  39. ????????{??
  40. ????????????cout?<<?"智能指針爸爸幫你釋放空間了"?<<?endl;??
  41. ????????????delete?_ptr;??
  42. ????????????delete?_num;??
  43. ????????????_ptr?=?NULL;??
  44. ????????????_num?=?NULL;??
  45. ????????}??
  46. ????}??
  47. ????~shared()??
  48. ????{??
  49. ????????Release();??
  50. ????}??
  51. protected:??
  52. ????T*?_ptr;??
  53. ????int*?_num;??
  54. };??
  55. ??
  56. int?main()??
  57. {??
  58. ????shared<int>ap1(new?int);??
  59. ????*ap1?=?2;??
  60. ????shared<int>ap2(ap1);??
  61. ????cout?<<?*ap2?<<?endl;??
  62. ????shared<int>ap3(new?int);??
  63. ????ap3?=?ap1;??
  64. ??
  65. }??

上面就是我實現的簡易的shared智能指針,現在我們調用這個智能指針,我們來看看結果:





我們發現它完美的解決了一切功能,這個指針真的算是很完美的思想,不過你再完美也會有瑕疵,要不然也不會boost::weak_ptr的存在,

boost::weak_ptr的存在就是為boost::shared_ptr解決一點點瑕疵的。這個問題藏得極深不會遇到的,但是當你真的遇到的時候,我相信你會

絞盡腦汁的找BUG,還是很難找的。話不多說,現在我們來看下面這個例子:

  1. struct?ListNode??
  2. {??
  3. ????int?_data;??
  4. ????shared_ptr<ListNode>?_prev;??
  5. ????shared_ptr<ListNode>?_next;??
  6. ??
  7. ????ListNode(int?x)??
  8. ????????:_data(x)??
  9. ????????,?_prev(NULL)??
  10. ????????,_next(NULL)??
  11. ????{}??
  12. ????~ListNode()??
  13. ????{??
  14. ????????cout?<<?"~ListNode"?<<?endl;??
  15. ????}??
  16. };??
  17. int?main()??
  18. {??
  19. ????shared_ptr<ListNode>?cur(new?ListNode(1));??
  20. ????shared_ptr<ListNode>?next(new?ListNode(2));??
  21. ????cur->_next?=?next;??
  22. ????next->_prev?=?cur;??
  23. ????cout?<<?"cur"?<<?"?????"?<<?cur.use_count()?<<?endl;??
  24. ????cout?<<?"next"?<<?"?????"?<<?next.use_count()?<<?endl;??
  25. ????return?0;??
  26. }??

現在我們驗證shared智能指針的缺陷,就不用我實現的那個了,那個好多功能我都沒實現,我們用專家
shared_ptr智能指針,構造兩個雙向鏈表里

面的結點,這里這個雙向鏈表可能有一點簡陋,但是我們只是需要它的prev和next指針就夠了。現在我們運行代碼看看會發生什么情況:



現在cur和next指針所管理的結點現在都有兩個指針指針管理,然后在這里會發生這樣一件事:



循環引用一般都會發生在這種"你中有我,我中有你"的情況里面,這里導致的問題就是內存泄漏,這段空間一直都沒有釋放,現在很明顯引用計數在這

里就不是很合適了,但是shared_ptr除了這里不夠完善,其他的地方都是非常有用的東西,所以編寫者在這里補充一個week_ptr,接下來我們看最后一

個智能指針week_ptr。


week_ptr==>


weak_ptr是為了配合shared_ptr而引入的一種智能指針,它更像是shared_ptr的一個助手而不是智能指針,為它不具有普通指針的行為,沒有重載

operator*和->,它的最大作用在于協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況.通俗一點講就是,首先weak_ptr?是專門為shared_ptr?

準備的。現在我們并不能根據內部的引用計數。weak_ptr??boost::shared_ptr?的觀察者對象,觀察者意味著weak_ptr?只對shared_ptr 進行引用

不改變其引用計數,當被觀察的shared_ptr?失效后,相應的weak_ptr?也相應失效,然后它就什么都不管光是個刪 , 也就是這里的cur和next在析

構的時候 , 不用引用計數減一 , 直接刪除結點就好。這樣也就間接地解決了循環引用的問題,當然week_ptr指針的功能不是只有這一個。但是現在

我們只要知道它可以解決循環引用就好。

現在總結一下:

1、在可以使用?boost?庫的場合下,拒絕使用?std::auto_ptr,因為其不僅不符合?C++?編程思想。


2、在確定對象無需共享的情況下,使用?boost::scoped_ptr。


3、在對象需要共享的情況下,使用?boost::shared_ptr。


4、在需要訪問?boost::shared_ptr?對象,而又不想改變其引用計數的情況下(循環引用)使用boost::weak_ptr。


5、最后一點,在你的代碼中,盡量不要出現?delete?關鍵字,因為我們有智能指針。



本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383793.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383793.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383793.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++(靜態)(常量)數據進行初始化問題以及靜態變量析構

在C11標準以前我們都不可以在類中對數據成員初始化&#xff0c;僅能在構造函數中進行初始化&#xff1a; class A {int a,b; double c; string d;A():a(1),b(2),c(3),d(""){} };在C11標準以后我們可以在類中對非靜態成員進行初始化。實際上的機制是在調用構造函數的…

C++this指針的用法

參考博客&#xff1a;https://www.cnblogs.com/zhengfa-af/p/8082959.html 在 訪問對象的非靜態成員時會隱式傳遞一個參數&#xff0c;即對象本身的指針&#xff0c;這個指針名為this。 例如&#xff1a; class A {int a1;public:A(){}void GetA(int a){cout<<this-&g…

C++開發者都應該使用的10個C++11特性

http://blog.jobbole.com/44015/ 感謝馮上&#xff08;治不好你我就不是獸醫 &#xff09;的熱心翻譯。如果其他朋友也有不錯的原創或譯文&#xff0c;可以嘗試推薦給伯樂在線。】 在C11新標準中&#xff0c;語言本身和標準庫都增加了很多新內容&#xff0c;本文只涉及了一些皮…

C++不能被聲明為虛函數

虛函數是為了實現多態&#xff0c;但是顯然并不是所有函數都可以聲明為虛函數的。 不能被聲明為虛函數的函數有兩類&#xff1a; 不能被繼承的函數不能被重寫的函數 因此&#xff0c;這些函數都不能被聲明為虛函數 普通函數構造函數 如果構造函數定義為虛函數&#xff0c;則…

類的聲明與定義

類的前向聲明&#xff1a; class A;在聲明之后&#xff0c;定義之前&#xff0c;類A是一個不完全類型&#xff0c;即知道A是一個類&#xff0c;但是不知道包含哪些成員。不完全類型只能以有限方式使用&#xff0c;不能定義該類型的對象&#xff0c;不完全類型只能用于定義指向…

shared_ptr的一些尷尬

http://blog.csdn.net/henan_lujun/article/details/8984543 shared_ptr在boost庫中已經有多年了&#xff0c;C11又為其正名&#xff0c;把他引入了STL庫&#xff0c;放到了std的下面&#xff0c;可見其頗有用武之地&#xff1b;但是shared_ptr是萬能的嗎&#xff1f;有沒有什…

C++轉換構造函數和類型轉換函數

參考博客&#xff1a;https://blog.csdn.net/feiyanaffection/article/details/79183340 隱式類型轉換 如果不同類型的數據在一起操作的時候編譯器會自動進行一個數據類型轉換。例如常用的基本數據類型有如下類型轉換關系&#xff1a; 轉換構造函數 構造函數有且僅有一個參數…

C++總結8——shared_ptr和weak_ptr智能指針

http://blog.csdn.net/wendy_keeping/article/details/75268687 智能指針的提出&#xff1a;智能指針是存儲指向動態分配對象指針的類&#xff0c;用于生存期控制。能夠確保正確銷毀動態分配的內存&#xff0c;防止內存泄露。 1.智能指針的分類&#xff1a; 不帶引用計數的智能…

C++析構函數執行順序

今天發現主程序中有多個對象時析構函數的執行順序不是對象定義的順序&#xff0c;而是對象定義順序反過來。 思考了一下&#xff0c;結合之前繼承、成員對象等的析構函數執行的順序&#xff0c;我覺得析構函數執行的順序為&#xff1a;構造函數的順序反過來&#xff0c;可能是…

c++寫時拷貝1

http://blog.csdn.net/SuLiJuan66/article/details/48882303 Copy On Write Copy On Write(寫時復制)使用了“引用計數”&#xff08;reference counting&#xff09;&#xff0c;會有一個變量用于保存引用的數量。當第一個類構造時&#xff0c;string的構造函數會根據傳入的參…

【C++學習筆記五】模板

模板是泛型編程的基礎 函數模板 模板定義以關鍵字template開始&#xff0c;后跟一個模板參數列表。這是一個逗號分隔的一個或多個模板參數的列表。用尖括號包圍起來。 模板函數定義的一般形式&#xff1a; template <class type> ret-tye func-name(parameter list) …

【Java學習筆記十】輸入輸出流

在Java.io包中提供了一系列用于處理輸入/輸出的流類。從功能上分為兩類&#xff1a;輸入流和輸出流。從六結構上可分為&#xff1a;字節流&#xff08;以字節為處理單位&#xff09;和字符流&#xff08;以字符為處理單位&#xff09;。 字符是由字節組成。在Java中所有字符用…

C++ 寫時拷貝 2

什么情況下會用到c中的拷貝構造函數】&#xff1a; 1&#xff09;用已經存在的同類的對象去構造出另一個新的對象 2&#xff09;當函數的形參是類的對象時&#xff0c;這時調用此函數&#xff0c;使用的是值的拷貝&#xff0c;也會調用拷貝構造函數 3&#xff09;當函數的返…

【Java學習筆記十一】圖形用戶界面

圖形用戶界面或圖形用戶接口(Graphical User Interface&#xff0c;GUI)是指采用圖形方式,借助菜單、按鈕等標準界面元素&#xff0c;用戶可以通過鼠標等外設向計算機系統發出指令、啟動操作&#xff0c;并將系統運行的結果同樣以圖形方式顯示給用戶的技術。 GUI是事件驅動的&…

C++ 寫時拷貝 3

http://blog.csdn.net/ljianhui/article/details/22895505 字符串一種在程序中經常要使用到的數據結構&#xff0c;然而在C中卻沒有字符串這種類型。在C中&#xff0c;為了方便字符串的使用&#xff0c;在STL中提供了一個string類。該類維護一個char指針&#xff0c;并封裝和提…

C++類模板實例化條件

&#xff08;我不想了解這個&#xff0c;可是考試要考。。。。 并不是每次使用模板類都會實例化一個類 聲明一個類模板的指針和引用不會引起類模板的實例化如果檢查這個指針或引用的成員時時&#xff0c;類模板會實例化定義一個對象的時候需要有類的定義&#xff0c;會實例化…

C++ String類寫時拷貝 4

http://blog.51cto.com/zgw285763054/1839752 維基百科&#xff1a; 寫入時復制&#xff08;英語&#xff1a;Copy-on-write&#xff0c;簡稱COW&#xff09;是一種計算機程序設計領域的優化策略。其核心思想是&#xff0c;如果有多個調用者&#xff08;callers&#xff09;同時…

C++筆試復習

基礎知識點 C中對象數組在定義的時候全部進行實例化&#xff08;與Java不同&#xff0c;Java相當于只是定義了一個指針數組&#xff0c;沒有進行實例化&#xff09; 程序的三種基本控制結構是&#xff1a;順序結構、循環結構、選擇結構 一個C程序開發步驟通常包括&#xff1a…

C++函數默認參數

聲明是用戶可以看到的部分&#xff0c;客戶非常信任地使用這個特性&#xff0c;希望得到一定的結果&#xff0c;但是你在實現里使用了不同的缺省值&#xff0c;那么將是災難性的。因此編譯器禁止聲明和定義時同時定義缺省參數值。 類的成員函數的參數表在聲明時默認參數位于參…

C語言鏈表各類操作詳解

http://blog.csdn.net/pf4919501/article/details/38818335鏈表概述   鏈表是一種常見的重要的數據結構。它是動態地進行存儲分配的一種結構。它可以根據需要開辟內存單元。鏈表有一個“頭指針”變量&#xff0c;以head表示&#xff0c;它存放一個地址。該地址指向一個元素。…