shared_ptr的注意點

對於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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章