自C++11起,C++標準庫提供了兩大類型的智能指針
一 shared_ptr
操作引用計數實現共享式擁有的概念。多個智能指針可以指向相同的對象,這個對象和其相關資源會在最後一個被銷燬時釋放。
class A {
public:
~A() {
cout << "釋放A" << endl;
}
};
void test() {
//自動釋放 引用計數爲1
shared_ptr<A> a(new A());
//退出方法 shared_ptr a本身釋放,對內部的 A 對象引用計數減1 則爲0 釋放new 出來的A 對象
}
雖然使用shared_ptr能夠非常方便的爲我們自動釋放對象,但是還是會出現一些問題。最典型的就是循環引用問題。
class B;
class A {
public:
~A() {
cout << "釋放A" << endl;
}
shared_ptr<B> b;
};
class B {
public:
~B() {
cout << "釋放B" << endl;
}
shared_ptr<A> a;
};
void test() {
//自動釋放
shared_ptr<A> a(new A()); //A引用計數爲1
shared_ptr<B> b(new B()); //B引用計數爲1
cout << a.use_count() << endl; //查看內部對象引用計數
a->b = b; //A 引用計數爲2
b->a = a; //B 引用計數爲2
//退出方法,a釋放,A引用計數-1結果爲1 不會釋放 B也一樣
}
weak_ptr
weak_ptr是爲配合shared_ptr而引入的一種智能指針。主要用於觀測資源的引用情況。
它的構造和析構不會引起引用記數的增加或減少。沒有重載*和->但可以使用lock獲得一個可用的shared_ptr對象。
配合shared_ptr解決循環引用問題
class B;
class A {
public:
~A() {
cout << "釋放A" << endl;
}
weak_ptr<B> b;
};
class B {
public:
~B() {
cout << "釋放B" << endl;
}
weak_ptr<A> a;
};
void test() {
//自動釋放
shared_ptr<A> a(new A()); //A引用計數爲1
shared_ptr<B> b(new B()); //B引用計數爲1
a->b = b; //weak_ptr 引用計數不增加
b->a = a; //weak_ptr 引用計數不增加
//退出方法,A B釋放
}
weak_ptr 提供expired 方法等價於 use_count == 0,當expired爲true時,lock返回一個存儲空指針的shared_ptr
二 unique_ptr
實現獨佔式引用,保證同一時間只有一個智能指針指向內部對象。
unique_ptr<A> a(new A());
auto_ptr已經不推薦使用
三 自定義智能指針
template <typename T>
class Ptr {
public:
Ptr() {
count = new int(1);
t = 0;
}
Ptr(T *t):t(t) {
//引用計數爲1
count = new int(1);
}
~Ptr() {
//引用計數-1 爲0表示可以釋放T了
if (--(*count) == 0)
{
if (t) {
delete t;
}
delete count;
t = 0;
count = 0;
}
}
//拷貝構造函數
Ptr(const Ptr<T> &p) {
//引用計數+1
++(*p.count);
t = p.t;
count = p.count;
}
Ptr<T>& operator=(const Ptr<T>& p) {
++(*p.count);
//檢查老的數據是否需要刪除
if (--(*count) == 0) {
if (t) {
delete t;
}
delete count;
}
t = p.t;
count = p.count;
return *this;
}
//重載-> 操作T 類
T* operator->() { return t; }
private:
T *t;
int *count;
};
重載=爲什麼返回引用,而不是對象?
return *this後馬上就調用拷貝構造函數,將*this拷貝給一個匿名臨時對象,然後在把臨時對象拷貝給外部的左值(a=b,a爲左值),再釋放臨時對象。這樣首先會造成不必要的開銷。
同時如果沒有自定義的拷貝函數,則會使用默認的淺拷貝。如果類中存在指向堆空間的成員指針,需要進行深拷貝(重新申請內存),避免相同內存地址被其他地方釋放導致的問題。
如果此處返回對象不會導致出現問題:
引用類型(Test1&) 沒有複製對象 返回的是 t 對象本身 t會被釋放 所以會出現問題(數據釋放不徹底就不一定)
這裏的t作用域是函數內我們自己創建的一個臨時對象,因此會被釋放,而在此處返回 *this,作用範圍和t不一樣。其次,在類中定義了拷貝函數,雖然未進行深拷貝,但小心的維護了堆內存指針(t、count),不會出現堆內存釋放導致的懸空指針情況。