從前面的內容可以看出auto_ptr具有比較多的缺陷,使用時容易出錯。在C++ 11標準中出現了新的智能指針unique_ptr、 shared_ptr與weak_ptr等
一、unique_ptr
特點:所有權唯一,禁止拷貝和權限轉移
缺點:unique_ptr是簡單粗暴的防止拷貝,這種比較簡單,效率高,但是功能不全面,不支持拷貝和賦值操作。無法共享數據,可自主實現數據共享,但在釋放時會導致堆內存重複釋放導致系統崩潰。
下面是unique_ptr的實現:
#include<iostream>
#include<memory>
template<typename T>
class Unique_Ptr
{
public:
Unique_Ptr(T* ptr)
:mptr(ptr)
{}
Unique_Ptr()
{
delete mptr;
mptr = NULL;
}
T& operator*()
{
return *mptr;
}
T* operator->()
{
return mptr;
}
private:
Unique_Ptr(Unique_Ptr<T>&);//將拷貝構造和賦值寫在私有下
Unique_Ptr<T>& operator=(Unique_Ptr<T>&);
T* mptr;
};
int main()
{
int* p = new int;
Unique_Ptr<int> up1(p);
Unique_Ptr<int> up2(p);
Unique_Ptr<int> up3(p);
return 0;
}
1.禁止賦值和複製
unique_ptr禁止賦值和複製,“唯一”地擁有其所指對象,同一時刻只能有一個unique_ptr實例指向給定對象。也就是說模板類unique_ptr的copy構造函數以及等號(“=”)操作符是無法使用的。
通過禁止複製和賦值可以較好的改善auto_ptr的所有權轉移問題。下面是其禁止複製和賦值的例子:
#include <iostream>
#include <memory>
using namespace std;
void Fun1( unique_ptr<int> up )
{
}
int main()
{
unique_ptr<int> up1 = unique_ptr<int>(new int(10));
//不允許複製,所以以下三個均錯誤
unique_ptr<int> up2 = up1; // error
unique_ptr<int> up3(up1); // error
Fun1(up1); // error
//不允許賦值('='),所以下面錯誤
unique_ptr<int> up4;
up4 = up1; // error
return 0;
}
2.針對auto_ptr缺陷的改善
1) 管理數組指針
因爲unique_ptr有unique_ptr< X[ ] >重載版本,銷燬動態對象時調用delete[],所以可以用unique_ptr來管理數組指針。
unique_ptr< Test[ ] > uptr1(new Test[3]);
//注意 unique_ptr<Test> uptr3(new Test[3]);是不對的
unique_ptr<int[]> uptr2(new int[5]);
2)做容器(vector, list, map)元素
vector<unique_ptr<int> > vec;
unique_ptr<int> ptr1(new int(3));
vec.push_back(std::move(ptr1));
//vec.push_back(ptr1); //由於禁止複製這樣不行
二、shared_ptr
特點:shared_ptr的實現原理是通過引用計數來實現,拷貝或賦值時將引用計數加1,析構時只有當引用計數減到0才釋放空間,否則只需將引用計數減1即可.shared_ptr共享所有權。
缺點:相互引用 內存泄漏
如圖理解引用計數和shared_ptr的關係:
#include<memory>
class Ref_Man //引用計數
{
public:
static Ref_Man* getInstance()
{
if (prm == NULL)
{
prm = new Ref_Man();
}
return prm;
}
int getRef(void* ptr)
{
if (ptr == NULL)
{
return -1;
}
int index = find(ptr);
if (index != -1)
{
return ref_count[index].count;
}
return -1;
}
void addRef(void* ptr)
{
if (ptr == NULL)
{
return;
}
int index = find(ptr);
if (index != -1)
{
ref_count[index].count++;
}
else
{
ref_count[cursize].addr = ptr;
ref_count[cursize++].count = 1;
}
}
void delRef(void* ptr)
{
if (ptr == NULL)
{
return;
}
int index = find(ptr);
if (index != -1)
{
if (getRef(ptr) != 0)
{
--ref_count[index].count;
}
}
}
private:
Ref_Man()
{
cursize = 0;
}
Ref_Man(const Ref_Man&);
int find(void* ptr)
{
int index = -1;
for (int i = 0; i < cursize; i++)
{
if (ref_count[i].addr == ptr)
{
index = i;
break;
}
}
return index;
}
typedef struct Ref
{
void* addr; //堆內存地址
int count; //堆內存所對應的指針數目
}Ref;
Ref ref_count[10];
int cursize;
static Ref_Man* prm;
};
Ref_Man* Ref_Man::prm = NULL;
template<typename T>
class Shared_Ptr
{
public:
Shared_Ptr(T* ptr = NULL) //構造
:mptr(ptr)
{
prm->addRef(mptr);
}
Shared_Ptr(Shared_Ptr<T>& rhs) //拷貝構造
{
mptr = rhs.mptr;
prm->addRef(mptr);
}
Shared_Ptr<T>& operator=(Shared_Ptr<T>&rhs) //賦值
{
if (this != &rhs)
{
prm->delRef(mptr);
if (prm->getRef(mptr))
{
delete mptr;
}
mptr = rhs.mptr;
prm->addRef(mptr);
}
return *this;
}
~Shared_Ptr()
{
prm->delRef(mptr); //--引用計數
if (prm->getRef(mptr) == 0) //獲取mptr引用計數,如果爲0釋放
{
delete mptr;
}
mptr = NULL;
}
T& operator*() //重載*
{
return *mptr;
}
T* operator->() //重載->
{
return mptr;
}
private:
T* mptr;
static Ref_Man* prm;
};
template<typename T>
Ref_Man* Shared_Ptr<T>::prm = Ref_Man::getInstance();
class B;
class A
{
public:
A()
{
std::cout << "A::A()" << std::endl;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
public:
std::weak_ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B::B()" << std::endl;
}
~B()
{
std::cout << "B::~B()" << std::endl;
}
public:
std::weak_ptr<A> spb;
};
int main()
{
std::shared_ptr<A> pa(new A());
std::shared_ptr<B> pb(new B());
pa->spa = pb; //相互引用
pb->spb = pa;
return 0;
}
int main()
{
int* p = new int; //使用weak_ptr後解決了相互引用導致內存泄漏的問題
Shared_Ptr<int> sp1(p);
Shared_Ptr<double> spd = new double;
Shared_Ptr<int> sp2(p);
Shared_Ptr<int> sp3(p);
return 0;
}
shared_ptr相互引用問題
- pa和pb兩個智能指針對象分別指向spa和spb,引用計數變成1.
- pa的spa指向pb,pb的spb指向pa,引用計數變成2。
- pa和pb析構,引用計數減到1,但是spa還指向spb。但是spb還指向spa。
- 也就是說spa析構了,pb就釋放了。
- 也就是說spb析構了,pa就釋放了。
- 但是spa屬於pa的成員,pa釋放了,spa纔會析構,而pa由spb管理,spb屬於pb成員,所以這就叫循環引用,誰也不會釋放。
weak_ptr的出現,很好的解決了這個問題。
三、weak_ptr
特點:weak_ptr是爲了配合shared_ptr而引入的一種智能指針,它更像是shared_ptr的一個助手而不是智能指針,因爲它沒有重載operator*和->,故而不具有普通指針的行爲。它的最大作用在於協助shared_ptr工作。
weak_ptr被設計爲與shared_ptr共同工作,可以從一個shared_ptr或者另一個weak_ptr對象構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指針引用計數的增加。
作用:解決強智能指針shared_ptr相互引用的問題。
代碼如下:
template<typename T>
Ref_Man* Shared_Ptr<T>::prm = Ref_Man::getInstance();
class B;
class A
{
public:
A()
{
std::cout << "A::A()" << std::endl;
}
~A()
{
std::cout << "A::~A()" << std::endl;
}
public:
std::weak_ptr<B> spa;
};
class B
{
public:
B()
{
std::cout << "B::B()" << std::endl;
}
~B()
{
std::cout << "B::~B()" << std::endl;
}
public:
std::weak_ptr<A> spb;
};
int main()
{
std::shared_ptr<A> pa(new A());
std::shared_ptr<B> pb(new B());
pa->spa = pb;
pb->spb = pa;
return 0;
}
int main()
{
int* p = new int;
Shared_Ptr<int> sp1(p);
Shared_Ptr<double> spd = new double;
Shared_Ptr<int> sp2(p);
Shared_Ptr<int> sp3(p);
return 0;
}
因爲在執行pa->spa = pb;pb->spb = pa;
時weak_ptr的spa和spb不會增加pa和pb的引用計數,這樣就保證了正常的釋放。
友情鏈接:
C++:RAII思想和智能指針(auto_ptr,unique_ptr,shared_ptr)詳解了引用計數部分,並且提供了線程安全的shared_ptr,很值得學習哦~