http://blog.csdn.net/wendy_keeping/article/details/75268687
智能指針的提出:智能指針是存儲指向動態分配對象指針的類,用于生存期控制。能夠確保正確銷毀動態分配的內存,防止內存泄露。
1.智能指針的分類:?
不帶引用計數的智能指針?
auto_ptr unique_ptr scoped_ptr
帶引用計數的智能指針?
shared_ptr:強智能指針?
weak_ptr:若智能指針
2.不帶引用計數的智能指針是如何確保正確釋放堆上內存的??
auto_ptr:
auto_ptr<A> ptr1(new A(10));
auto_ptr<A> ptr2 = ptr1;
- 1
- 2
當這兩行代碼執行完后,ptr1已經不再指向A這個對象,并且ptr1=NULL,ptr1對A對象的所有權已經轉移給了ptr2,也就不能通過ptr1調用該類的方法。
vector<auto_ptr<int>> vec;
不能將auto_ptr類型的指針作為STL容器的元素。因為容器避免不了直接拷貝構造和互相賦值。auto_ptr的這種特性會給STL容器的使用造成很大的麻煩。
unique_ptr:
unique_ptr<A> ptr1(new A(10));
unique_ptr<A> ptr2 = ptr1;//error
unique_ptr<A> ptr3 = func();//ok
unique_ptr不支持拷貝構造函數和賦值運算符重載函數,不能隱式的將原指針置為NULL,但是可以通過返回值賦值。unique_ptr支持移動構造函數和移動賦值運算符重載函數,是通過move(C++11標準)函數實現的,用戶明確知道對象的所有權已經發生轉移,不能再使用原來的指針調用類的方法。這也是auto_ptr和unique_ptr最根本的區別。
scoped_ptr:
scoped_ptr不支持所有權的轉移,只能通過該指針對對象進行操作,出作用域會自動析構。
3.帶引用計數的智能指針
shared_ptr<A> pa(new A());
shared_ptr<A> pb = pa;
當使用指針指向堆上的對象時,該對象的引用計數加1。拷貝構造或賦值時,不產生該對象的副本,只是更改對象的引用計數。如果別的智能指針也指向這個對象時,也是增加其引用計數。如果delete 指針時,先對引用計數減1,直到對象的引用計數減為0,才調用該對象的析構函數。
4.智能指針的交叉引用導致內存泄露
class A
{
public:shared_ptr<B> _pb;
};
class B
{
public:shared_ptr<A> _pa;
};int main()
{shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->_pb = pb;pb->_pa = pa;return 0;
}
pa指針指向堆上對象后,A的引用計數是1,B的引用計數是1。?
pa->_pb = pb;此時,B的引用計數是2。?
pb->_pa = pa;此時,A的引用計數是2。
出作用域后:?
pb指針釋放,B對象的引用計數減1,此時,B的引用計數是1,并不調用B的析構函數。?
pa指針釋放,A對象的引用計數減1,此時,A的引用計數是1,并不調用A的析構函數。
程序運行結束,A和B對象并未析構,造成內存泄露
為了避免內存泄露,必須打斷這種循環等待的現象。智能指針提供了weak_ptr(弱智能指針)可以避免產生循環等待的現象。
weak_ptr,當用weak_ptr指向對象時,對象的引用計數并不加1,因為弱智能指針沒有對對象的使用權,它只有監控權。
#include <iostream>
#include <memory>
using namespace std;class A
{
public:weak_ptr<B> _pb;
};
class B
{
public:shared_ptr<A> _pa;
};int main()
{shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->_pb = pb;pb->_pa = pa;return 0;
}
pa指針指向堆上對象后,A的引用計數是1,B的引用計數是1。?
pa->_pb = pb;此時,B的引用計數是1。因為A的成員對象是weak_ptr。?
pb->_pa = pa;此時,A的引用計數是2。
出作用域后:?
pb指針釋放,B對象的引用計數減1,此時,B的引用計數是0,調用B的析構函數,先析構自己(B被析構),再析構成員對象(A的引用計數減1,此時A的引用計數是1)?
pa指針釋放,A對象的引用計數減1,此時,A的引用計數是0,調用A的析構函數(A被析構)
為了避免智能指針循環引用造成的內存泄露情況,要遵守:創建對象的時候,一定要用強智能指針,其他地方只能用持有資源的弱智能指針。
智能指針本身是線程安全的。使用atomic_increment和atimic_decreament進行對多線程間的互斥,保證引用計數count++/count–是原子操作。
5.多線程訪問共享的C++對象產生的線程安全的問題
#include <iostream>
#include <memory>
using namespace std;class C
{
public:C(){}~C(){}void func(){cout<<"call func()"<<endl;}
};void* threadProc(void *lparg)
{shared_ptr<C> pc = (shared_ptr<C>)lparg;pc->func();//如果對象已經析構,則程序崩潰!return NULL;
}int main(int argc, char* argv[])
{shared_ptr<C> c(new C());pthread_t tid;pthread_create(&tid, NULL, threadProc, &c);return 0;
}
多線程中可能訪問共享的對象,如果對象已經析構,線程中訪問它,程序會崩潰掉。解決多線程訪問共享的C++對象產生的線程安全的問題,可以使用weak_ptr。將弱智能指針轉給線程,再訪問對象時,先將弱智能和提升為強智能指針,如果提升成功,則說明對象還存在,可以訪問該對象的成員變量或方法。如果提升失敗,則說明對象已經析構。
#include <iostream>
#include <memory>
using namespace std;class C
{
public:C(){}~C(){}void func(){cout<<"call func()"<<endl;}
};void* threadProc(void *lparg)
{weak_ptr<C> pw = *(weak_ptr<C>*)lparg;shared_ptr<C> pc = pw.lock();
//類型提升if(pc != NULL)
//類型提升成功,對象未析構{pc->func();}return NULL;
}int main(int argc, char* argv[])
{shared_ptr<C> c(new C());pthread_t tid;weak_ptr<C> pw(c);pthread_create(&tid, NULL, threadProc, &pw);return 0;
}
創建對象的時候一定要用強智能指針,其他地方只能用持有資源的弱智能指針!!!