對於C++新手而言,面對複雜的項目中指針的四處傳遞,或者異常後的處理,很容易引起申請了內存沒有釋放的問題,c++11給出了智能指針來簡化這一問題,常用的是shared_ptr。shared_ptr構造出的對象來管理一塊內存,結構如下:
其中ptr指向了一塊內存空間,ret_count存儲了有多少shared_ptr對象引用了這塊內存。當引用計數爲0時,刪除這塊內存。但是使用它如果用的不恰當,也會引發一些問題,我個人覺得,如果要使用shared_ptr,那就全部用它,不要再用裸指針 。因爲裸指針不是通過shared_ptr對象的傳遞那就不會改變它的引用計數。 比如下面的代碼:
int *q;
{
int *p = new int(10);
q = p;
shared_ptr<int> sp(p);
cout << sp.use_count()<<endl;// use_count等於1
}
cout << *q;
在局部作用域結束後,sp釋放了引用的那塊內存,此時再訪問q已經失效了。而如果改成下面的寫法,就可以正確訪問,並且正確釋放內存。
shared_ptr<int> q;
{
int *p = new int(10);
shared_ptr<int> sp(p);
q = sp;
cout << sp.use_count()<<endl;
}
cout << *q;
同樣的,像這種問題還有,比如用shared_ptr指向同一塊裸指針分配的空間時也會出現問題,比如下面的代碼
{
char*p = new char[128];
shared_ptr<char> sp(p);
shared_ptr<char> sp2 (p);
cout << sp.use_count(); // 1
cout << sp2.use_count(); // 1
//此處會兩次調用delete p;
}
在局部作用域結束後,其實兩個shared_ptr的引用計數都爲1,所以就會調用兩次delete,就會引發異常。
shared_ptr中有一個get函數,可以獲得其原始指針,但我覺得get後最好不要賦值給其他指針,否則可能會寫出下面的代碼:
shared_ptr<int> p = make_shared<int>(42);
cout << *p;
int *q = p.get();
{
shared_ptr<int> sp(q);
} // 程序塊結束,q被銷燬,指向的內存被釋放。
cout << *p;//此時p內存已被釋放,此處引用一個野指針會異常
在局部作用域中sp引用計數爲1,結束後銷燬q指向的內存,q是p的get獲得,所以就是釋放的p的內存,此時再訪問p就會出問題。
類似的,還有下面的情況,不要將get後的指針在reset中引用
{
shared_ptr<int>sp(new int(10));
shared_ptr<int>sp2(new int(20));
sp.reset(sp2.get());//此處會釋放sp的內存
cout << *sp << sp.use_count() << endl;
cout << *sp2 << sp2.use_count() << endl;
}
第三行代碼處sp調用reset後會刪除sp本身所在的內存,然後引用sp2的內存,輸出時*sp與*sp2都爲20,但是由於通過get方法構造的,他們的use_count都爲1,所以在程序塊結束時分別調用delete又會刪除同一塊內存兩次。
另外就是循環引用問題
引用其他文章的一段代碼
class parent;
class children;
typedef shared_ptr<parent> parent_ptr;
typedef shared_ptr<children> children_ptr;
class parent
{
public:
~parent() { std::cout << "destroying parent\n"; }
public:
//weak_ptr<children> children;
children_ptr children;
};
class children
{
public:
~children() { std::cout << "destroying children\n"; }
public:
parent_ptr parent;
//weak_ptr<parent> parent;
};
void test()
{
A a;
B b;
parent_ptr father(new parent());
children_ptr son(new children);
father->children = son;
cout << son.use_count() << endl;
son->parent = father;
cout << father.use_count() << endl;
}
void main()
{
std::cout << "begin test...\n";
test();
std::cout << "end test.\n";
cin.get();
}
原文:https://blog.csdn.net/leichaowen/article/details/53064294
由於father類中持有son類的shared_ptr,son類中也持有father類的shared_ptr,導致在作用域結束時引用計數都爲2,引起內存泄露,解決辦法就是將成員變量改爲weak_ptr,week_ptr專門爲了配合shared_ptr而生,它不修改引用計數,可通過expired函數檢查是否有效。
最後就是在多線程下的問題,shared_ptr的引用計數本身是安全且無鎖的。但是對於讀寫則需要加鎖,引用一段代碼:
shared_ptr<long> global_instance = make_shared<long>(0);
std::mutex g_i_mutex;
void thread_fcn()
{
//std::lock_guard<std::mutex> lock(g_i_mutex);
//shared_ptr<long> local = global_instance;
for(int i = 0; i < 100000000; i++)
{
*global_instance = *global_instance + 1;
//*local = *local + 1;
}
}
int main(int argc, char** argv)
{
thread thread1(thread_fcn);
thread thread2(thread_fcn);
thread1.join();
thread2.join();
cout << "*global_instance is " << *global_instance << endl;
return 0;
}
在線程函數thread_fcn的for循環中,2個線程同時對*global_instance進行加1的操作。這就是典型的非線程安全的場景,最後的結果是未定的,運行結果如下:
*global_instance is 197240539
如果使用的是每個線程的局部shared_ptr對象local,因爲這些local指向相同的對象,因此結果也是未定的,運行結果如下:
*global_instance is 160285803
因此,這種情況下必須加鎖,將thread_fcn中的第一行代碼的註釋去掉之後,不管是使用global_instance,還是使用local,得到的結果都是:
*global_instance is 200000000
https://stackoverflow.com/questions/14482830/stdshared-ptr-thread-safety