C++98標准中的auto_ptr,但是隨著C++11的到來,auto_ptr已經不再了,即將成為歷史;好的東西總是會受到大家的歡迎的,隨著大家都在使用“准”標准庫boost中的shared_ptr;C++標准委員會終於覺的是時候將shared_ptr加入到C++11中去了。歡呼聲一片,至少我是這麼覺的了;至少shared_ptr讓我用起來,還是不錯的。接下來,就總結一下C++11中的這些智能指針吧。
C++11有哪些智能指針
先來一段簡單的代碼,看看C++11中到底有哪些智能指針。
/*************************************************************************
> File Name: SmartPointDemo.cpp
> Author: Jelly
> Mail: vipygd#126.com(#->@)
> Created Time: 2014年10月16日 星期四 15時25分43秒
************************************************************************/
#include <iostream>
#include <memory>
using namespace std;
int main()
{
unique_ptr<int> up1(new int(10)); // 不能復制的unique_ptr
// unique_ptr<int> up2 = up1; // 這樣是錯的
cout<<*up1<<endl;
unique_ptr<int> up3 = move(up1); // 現在up3是數據唯一的unique_ptr智能指針
cout<<*up3<<endl;
// cout<<*up1<<endl; // 運行時錯誤
up3.reset(); // 顯示釋放內存
up1.reset(); // 即使up1沒有擁有任何內存,但是這樣調用也沒有問題
// cout<<*up3<<endl; // 已經釋放掉up3了,這樣會運行時錯誤
shared_ptr<int> sp1(new int(20));
shared_ptr<int> sp2 = sp1; // 這是完全可以的,增加引用計數
cout<<*sp1<<endl;
cout<<*sp2<<endl;
sp1.reset(); // 減少引用計數
cout<<*sp2<<endl;
return 0;
}
C++11中主要提供了unique_ptr、shared_ptr和weak_ptr這三個智能指針來自動回收堆分配的對象。看看上面的代碼,感覺用起來也還挺輕松的,也還不錯,至少是比auto_ptr好點。
unique_ptr
C++11中的unique_ptr是auto_ptr的替代品,它與auto_ptr一樣擁有唯一擁有權的特性,與auto_ptr不一樣的是,unique_ptr是沒有復制構造函數的,這就防止了一些“悄悄地”丟失所有權的問題發生,如果需要將所有權進行轉移,可以這樣操作:
unique_ptr<int> up3 = move(up1); // 現在up3是數據唯一的unique_ptr智能指針
// 或者
unique_ptr<int> up4(move(up1));
只有在使用者顯示的調用std::move之後,才會發生所有權的轉移,這樣就讓使用者知道自己在干什麼。再來一段代碼,看看將unique_ptr作為函數參數和返回值的使用情況:
/*************************************************************************
> File Name: unique_ptrDemo.cpp
> Author: Jelly
> Mail: vipygd#126.com(#->@)
> Created Time: 2014年10月16日 星期四 17時10分49秒
************************************************************************/
#include <iostream>
#include <memory>
using namespace std;
unique_ptr<int> Func(unique_ptr<int> a)
{
cout<<*a<<endl;
return a;
}
int main()
{
unique_ptr<int> up1(new int(10));
up1 = Func(move(up1));
cout<<*up1<<endl;
return 0;
}
由於在unique_ptr中是沒有復制構造函數的,所以在直接傳參時,進行值傳遞時,建立臨時變量時,就會出錯了,所以需要顯示的調用move,轉移所有權;而函數的返回值已經進行了move操作,而不用顯示的進行調用。
shared_ptr
在最開始的那段代碼中,也簡單的使用了一下shared_ptr。shared_ptr名如其名,它允許多個該智能指針共享地“擁有”同一堆分配對象的內存;由於它的資源是可以共用的,所以也就可以透過operator=等方法,來分享shared_ptr所使用的資源。由於shared_ptr內部實現上使用的是引用計數這種方法,所以一旦一個shared_ptr指針放棄了“所有權”,其它的shared_ptr對對象的引用並不會發生變化;只有在引用計數歸零的時候,shared_ptr才會真正的釋放所占有的堆內存空間的。對於引用計數的問題,我這裡就不再多總結了,可以參考以下文章:
– 智能指針-引用計數實現
– COM中的引用計數1
– COM中的引用計數2
我這裡注重的總結shared_ptr的使用,並不會對shared_ptr進行源碼級別的分析。再來一段簡單的代碼,看看shared_ptr的一些應用。
#include <iostream>
#include <memory>
using namespace std;
void Func1(shared_ptr<int> a)
{
cout<<"Enter Func1"<<endl;
cout<<"Ref count: "<<a.use_count()<<endl;
cout<<"Leave Func1"<<endl;
}
shared_ptr<int> Func2(shared_ptr<int> a)
{
cout<<"Enter Func2"<<endl;
cout<<"Ref count: "<<a.use_count()<<endl;
cout<<"Leave Func2"<<endl;
return a;
}
int main()
{
shared_ptr<int> aObj1(new int(10));
cout<<"Ref count: "<<aObj1.use_count()<<endl;
{
shared_ptr<int> aObj2 = aObj1;
cout<<"Ref count: "<<aObj2.use_count()<<endl;
}
Func1(aObj1);
Func2(aObj1);
shared_ptr<int> aObj3 = Func2(aObj1);
cout<<"Ref count:"<<aObj3.use_count()<<endl;
return 0;
}
自己單獨想想程序的輸出。輸出如下:
Ref count: 1
Ref count: 2
Enter Func1
Ref count: 2
Leave Func1
Enter Func2
Ref count: 2
Leave Func2
Enter Func2
Ref count: 2
Leave Func2
Ref count:2
shared_ptr指向數組
在默認情況下,shared_ptr將調用delete進行內存的釋放;當分配內存時使用new[]時,我們需要對應的調用delete[]來釋放內存;為了能正確的使用shared_ptr指向一個數組,我們就需要定制一個刪除函數,例如:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A() { cout<<"constructor"<<endl; }
~A() { cout<<"destructor"<<endl; }
};
int main()
{
shared_ptr<A> arrayObj(new A[5], [](A *p){delete[] p;});
return 0;
}
上面的代碼看不懂的,請參考這篇《C++中的Lambda表達式》文章。如果確實需要共享地托管一個對象,使用unique_ptr也許會更簡單一些,比如:
#include <iostream>
#include <memory>
using namespace std;
class A
{
public:
A() { cout<<"constructor"<<endl; }
~A() { cout<<"destructor"<<endl; }
};
int main()
{
unique_ptr<A[]> arrayObj(new A[5]);
return 0;
}
線程安全
關於多線程中使用shared_ptr,有如下幾點描述:
1. 同一個shared_ptr被多個線程讀,是線程安全的;
2. 同一個shared_ptr被多個線程寫,不是 線程安全的;
3. 共享引用計數的不同的shared_ptr被多個線程寫,是線程安全的。 對於第一點,沒有什麼說的;對於第二點,同一個shared_ptr在不同的線程中進行寫操作不是線程安全的,那基於第三點,我們一般會有以下方案來實現線程安全:
對於線程中傳入的外部shared_ptr對象,在線程內部進行一次新的構造,例如: sharedptr AObjTmp = outerSharedptrObj;
環形引用
對於使用引用計數實現的智能指針,總是避免不了這個問題的。如果出現類似下面的代碼,那就出現了環形引用的問題了。
class Parent
{
public:
shared_ptr<Child> child;
};
class Child
{
public:
shared_ptr<Parent> parent;
};
shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;
要解決環形引用的問題,沒有特別好的辦法,一般都是在可能出現環形引用的地方使用weak_ptr來代替shared_ptr。說到了weak_ptr,那下面就接著總結weak_ptr吧。
weak_ptr
weak_ptr是最麻煩的,也比較拗口的;它可以指向shared_ptr指針指向的對象內存,卻並不擁有該內存。但是,使用weak_ptr成員lock,則可返回其指向內存的一個shared_ptr對象,且在所指對象內存已經無效時,返回指針空值(nullptr)。由於weak_ptr是指向shared_ptr所指向的內存的,所以,weak_ptr並不能獨立存在。例如以下代碼:
#include <iostream>
#include <memory>
using namespace std;
void Check(weak_ptr<int> &wp)
{
shared_ptr<int> sp = wp.lock(); // 重新獲得shared_ptr對象
if (sp != nullptr)
{
cout<<"The value is "<<*sp<<endl;
}
else
{
cout<<"Pointer is invalid."<<endl;
}
}
int main()
{
shared_ptr<int> sp1(new int(10));
shared_ptr<int> sp2 = sp1;
weak_ptr<int> wp = sp1; // 指向sp1所指向的內存
cout<<*sp1<<endl;
cout<<*sp2<<endl;
Check(wp);
sp1.reset();
cout<<*sp2<<endl;
Check(wp);
sp2.reset();
Check(wp);
return 0;
}
所以,我們在使用weak_ptr時也要當心,時刻需要判斷對應的shared_ptr是否還有效。對於上面的環形引用的問題,由於weak_ptr並不會增加shared_ptr的引用計數,所以我們就可以使用weak_ptr來解決這個問題。
class Parent
{
public:
weak_ptr<Child> child;
};
class Child
{
public:
weak_ptr<Parent> parent;
};
shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;
總結
這篇文章有點長,但是很詳細的總結了C++11中的智能指針相關的問題,希望這篇文章對大家有用。