shared_ptr簡介以及常見問題

http://blog.csdn.net/stelalala/article/details/19993425

本文中的shared_ptr以vs2010中的std::tr1::shared_ptr作為研究對象。可能和boost中的有些許差異,特此說明。

基本功能

shared_ptr提供了一個管理內存的簡單有效的方法。shared_ptr能在以下方面給開發提供便利:

1、 使用shared_ptr能有效的解決忘記釋放內存帶來的內存泄漏問題。同時通過自定義刪除器功能還能廣泛的用于任何需要”釋放”的資源管理。

2、 利用weak_ptr和shared_ptr搭配使用能解決一部分由于重復釋放導致的野指針問題。

基本構造方式

不談拷貝構造的話,shared_ptr的基本構造方式有4種:

1、 無參數構造。

2、 傳入一個指針構造一個shared_ptr。例如shared_ptr<Foo> f(new Foo)。

3、 傳入一個指針和一個刪除器構造一個shared_ptr。例子見后文。

4、 傳入一個指針、一個刪除器以及一個allocator構造一個shared_ptr。

當然還有一些其他的,例如從auto_ptr從weak_ptr從null_ptr構造。

另外,類似于void*,shared_ptr<void>可以容納任何類型的指針。

其他常用方法

l use_count()方法。獲取到當前智能指針的引用計數。0表示沒有任何地方引用。

l get()方法。獲取到raw指針。

l reset()方法。重新設置智能指針指向的對象,引用計數重新設置為1。reset方法的函數原型有4種,基本上和前文提到的4種構造函數一一對應。

l swap()方法。交換兩個智能指針的內容。

l operater =()方法。該方法會涉及到引用計數的增加。

關于自定義刪除器

智能指針能夠自定義刪除器是一個很重要的功能,該功能使得能夠跨dll傳遞shared_ptr變為可能(當然前提是多個dll使用的shared_ptr實現要一樣)。尤其是當c++11的Lambda表達式出現后這個功能用起來更加方便。

先來看自定義刪除器的構造方法:

template<class _Ux,

class _Dx>

shared_ptr(_Ux *_Px, _Dx _Dt)

{ // construct with _Px, deleter

_Resetp(_Px, _Dt);

}

其中構造函數的第二個參數就是刪除器。這里要求刪除器:

1、 是”可調用”的即可,例如function object、函數指針、Lambda表達式、bind/functor等等均可。

2、 返回值是void,參數是Ux*

3、 從形參看出,刪除器以傳值的方式傳入,所以要求刪除器要是可拷貝的,否則會編譯出錯。

4、 刪除器不要拋出異常。

例如:

shared_ptr<Foo> shot1(new Foo(1),[&](Foo* p){p->Release();});

make_shared有何用處

boost或者stl都提供了make_shared這個函數。用來方便的創建shared_ptr。

make_shared的好處有兩點:

1、 既然用了shared_ptr不用手動delete指針,那么最好也不要在代碼中出現new。make_shared正是在函數內封裝了new的操作。

2、 從shared_ptr的數據接口了解到,在構造shared_ptr的時候,會new出一個對象保存指針的相關信息。所以一般來說,shared_ptr<Foo> x(new Foo); 需要為Foo 和ref_count 各分配一次內存。如果使用make_shared來創建的話,make_shared內部會盡量將兩次內存分配在連續的位置(這個得看用的什么heap管理)。這里理論上能夠更快一些。

說下缺點:

1、 make_shared只能針對new出來的,對于使用工廠創建出來的對象無能為力。

2、 需要定制刪除器時,make_shared無能為力。

3、 make_shared目前只支持10個參數

另外,make_shared代碼很有意思,為了方便的定義10個參數,宏定義用得鬼斧神工。

如何進行類型cast

如果只能指針聲明為基類的指針,指向的實際類型是子類的話,shared_ptr會自動完成。其他的轉型一眼就能看明白,無需多言:

tr1::const_pointer_cast

tr1::dynamic_pointer_cast

tr1::static_pointer_cast

使用shared_ptr可能會遇到的問題

生命周期的問題

使用shared_ptr的目的就是管理對象的生命周期。在使用了shared_ptr以后有幾個事情會變得和以往不太一樣。

首先,用了shared_ptr就表明對象是使用引用計數來管理,那么該對象什么時候真正被從內存中釋放掉就不是很明顯了。比如說,可能你的代碼中持有了一份shared_ptr的拷貝,就會導致某個對象一直存留下來。

shared_ptr多次引用同一數據

發生這樣的事情后,最好的下場是:后釋放的shared_ptr在析構的時候吐核。

在實際編碼中要注意。不要把一個raw指針交給多個shared_ptr管理。發生這樣的事情很可能是在遺留代碼上使用新特性導致的。

this指針的問題

例如這樣的例子:

class Foo

{

public:

Foo* GetThis()

{

return this;

}

}

要把這樣的代碼改為返回shared_ptr<Foo>,不那么好改。假如直接這樣修改會有嚴重的問題:

shared_ptr<Foo> GetThis()

{

return shared_ptr<Foo>(this);

}

因為shared_ptr<Foo>被使用完后就析構了,引用計數減到0以后就會把this delete掉。照成野指針。

為了解決這個問題,標準庫提供了一個方法:讓類派生自一個模板類:enable_shared_from_this<T>。然后調用shared_from_this()函數即可。

class Foo: public enable_shared_from_this<Foo>

{

public:

shared_ptr<Foo> GetThis()

{

return shared_from_this();;

}

}

這個方法看上去不那么美觀,但是確實解決了一些問題。也帶來了另一些問題:shared_from_this()這個函數不能夠在構造函數中調用。具體原理下一篇文章剖析shared_ptr實現原理時再講吧。

多線程的問題

shared_ptr的線程安全的定義在boost的文檔中有明確的說明:

l 一個shared_ptr對象可以被多個線程同時read

l 兩個shared_ptr對象,指向同一個raw指針,兩個個線程分別write這兩個shared_ptr對象,是安全的。包括析構。

l 多個線程如果要對同一個shared_ptr對象讀寫,是線程不安全的

也就是說,唯一需要注意的就是:多個線程中對同一個shared_ptr對象讀寫時需要加鎖。但是即使是加鎖也有技巧。比較好的方式是:

thread.lock();

shared_ptr tmpPtr=globalSharedPtr; // globalSharedPtr是多個線程讀寫的那個

thread.unlock();

后面的操作均針對tmpPtr進行

環形引用的問題

環形引用是指這樣的情況:

Class A的一個實例中持有一個shared_ptr<B>,Class B的一個實例中持有shared_ptr<A>。考慮以下代碼:

class CParent

{

public:

shared_ptr< CChild > children;

};

class CChild

{

public:

shared_ptr< CParent > parent;

};

int main()

{

{

shared_ptr< CParent > pA(new CParent);

shared_ptr< CChild > pB(new CChild);

pA-> children =pB;

pB-> parent =pA;

}

//到這里pA和pB都未能被釋放掉

}

要解決環形引用,沒有特別好的辦法。在分析代碼以后,知道了在某個地方可能有環形引用,那么可以使用weak_ptr來替代shared_ptr。

weak_ptr

weak_ptr本身不具有指針的行為,例如你不能對一個weak_ptr來進行*或者->操作。它通常用來和shared_ptr配合使用。

weak_ptr作為一個”shared_ptr的觀察者”能夠獲知shared_ptr的引用計數,還可以獲知一個shared_ptr是否已經被析構了。單沖這一點來說,就一點不weak了。

構造weak_ptr

有兩種方法可以構造一個weak_ptr

1、 從shared_ptr構造而來。這種情況不會增加shared_ptr的引用計數。當然會增加另一個計數,這個放到下一篇中講。

2、 從另一個weak_ptr拷貝。

也就是說weak_ptr不可能脫離shared_ptr而存在。

expired()

返回布爾,當返回true的時候表示,weak_ptr關聯的shared_ptr已經被析構了。

int _tmain(int argc, _TCHAR* argv[])

{

shared_ptr<foo> fptr=shared_ptr<foo>(new foo(1,2));

weak_ptr<foo> wptr=fptr;

fptr.reset();

if(wptr.expired())

{

cout<<”wptr has expired”<<endl;

}

system(“pause”);

return 0;

}

lock()

從當前的weak_ptr創建一個新的shared_ptr。如果此時expired()返回true時,創建的shared_ptr中將保存一個null_ptr。

use_count()

返回當前關聯的shared_ptr的引用計數是多少。expired()返回true時,該函數返回0。

weak_ptr使用場景

weak_ptr的特性是:weak_ptr不會增加shared_ptr的引用計數,所以weak_ptr通常用來解決shared_ptr無法解決的問題,例如環形引用。weak_ptr常見的使用場景有這么幾個:

1、 想管理某些資源,但是又不想增加引用計數,那么就可以保存weak_ptr。

2、 當知道了有環形引用后,可以使用weak_ptr。例如上面的例子可以改為這樣:

class CParent

{

public:

shared_ptr< CChild > children;

};

class CChild

{

public:

weak_ptr< CParent > parent;

};

int main()

{

{

shared_ptr< CParent > pA(new CParent);

shared_ptr< CChild > pB(new CChild);

pA-> children =pB;

pB-> parent =pA;

}

}

3、 某些情況下,需要知道某個shared_ptr是否已經釋放了。

總結

1、 在遺留代碼上如果要引入shared_ptr要謹慎!shared_ptr帶來的不確定性可能要比帶來的便利性大的多。

2、 使用shared_ptr并不是意味著能偷懶。反而你更需要了解用shared_ptr管理的對象的生命周期應該是什么樣子的,是不是有環形引用,是不是有線程安全問題,是不是會在某個地方意外的被某個東西hold住了。

3、 一個對象如何使用shared_ptr管理那么最好全部使用shared_ptr來管理,必要的時候可以使用weak_ptr。千萬不要raw ptr和智能指針混用

3、 不要以傳遞指針的形式傳遞shared_ptr。

4、 多線程讀寫同一個shared_ptr的時候,可以先加鎖拷貝一份出來,然后解鎖即可。

參考

1、 1、《Boost程序庫完全開發指南》

2、 當析構函數遇到多線程──C++ 中線程安全的對象回調

http://www.cnblogs.com/Solstice/archive/2010/02/10/dtor_meets_threads.html

3、 為什么多線程讀寫shared_ptr 要加鎖

http://www.cnblogs.com/Solstice/archive/2013/01/28/2879366.html

4、 vc stl


————————以上文章轉自:shared_ptr簡介以及常見問題


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

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

相關文章

【Java學習筆記九】多線程

程序&#xff1a;計算機指令的集合&#xff0c;它以文件的形式存儲在磁盤上&#xff0c;是應用程序執行的藍本。 進程&#xff1a;是一個程序在其自身的地址空間中的一次執行活動。進程是資源申請、調度和獨立運行的單位&#xff0c;因此&#xff0c;它使用系統中的運行資源。而…

【C++11新特性】 C++11智能指針之weak_ptr

http://blog.csdn.net/xiejingfa/article/details/50772571 原創作品&#xff0c;轉載請標明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50772571 如題&#xff0c;我們今天要講的是C11引入的三種智能指針中的最后一個&#xff1a;weak_ptr。在學習weak_ptr之…

【C++學習筆記四】運算符重載

當調用一個重載函數和重載運算符時&#xff0c;編譯器通過把您所使用的參數類型和定義中的參數類型相比較&#xff0c;巨鼎選用最合適的定義。&#xff08;重載決策&#xff09; 重載運算符時帶有特殊名稱的函數&#xff0c;函數名是由關鍵字operator和其后要重載的運算符符號…

【C++11新特性】 C++11智能指針之unique_ptr

原創作品&#xff0c;轉載請標明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50759210 在前面一篇文章中&#xff0c;我們了解了C11中引入的智能指針之一shared_ptr&#xff0c;今天&#xff0c;我們來介紹一下另一種智能指針unique_ptr。 unique_ptr介紹 uni…

C++派生類對象和基類對象賦值

在C中&#xff0c;我們允許 將派生類對象賦給基類對象。&#xff08;不允許將基類對象賦給派生類對象&#xff09; 只會將基類對象成員賦值用基類指針指向派生類對象。&#xff08;不允許用派生類指針指向基類對象&#xff09; 基類指針只能操作基類中的成員基類引用作為派生類…

【C++11新特性】 C++11智能指針之shared_ptr

http://blog.csdn.net/Xiejingfa/article/details/50750037 原創作品&#xff0c;轉載請標明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50750037 C中的智能指針首先出現在“準”標準庫boost中。隨著使用的人越來越多&#xff0c;為了讓開發人員更方便、更安…

C++(純)虛函數重寫時訪問權限更改問題

我們知道在Java中是自動實現多態的&#xff0c;Java中規定重寫的方法的訪問權限不能縮小。那么在C中我們實現多態的時候是否可以更改&#xff08;縮小&#xff09;訪問權限呢&#xff1f; 經過測試&#xff0c;得到的答案如下&#xff1a;如果用基類指針指向派生類對象實現多態…

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

http://blog.csdn.net/dawn_sf/article/details/70168930 智能指針 ____________________________________________________ 今天我們來看一個高大上的東西&#xff0c;它叫智能指針。 哇這個名字聽起來都智能的不得了&#xff0c;其實等你了解它你一定會有一點失望的。。。。因…

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中所有字符用…