智能指針的基本思想:
將堆對象的生存周期用棧對象(智能指針)來管理.當new一個堆對象時,立刻用智能指針來接管,具體的做法是在構(gòu)造函數(shù)中進行初始化(用一個指針指向堆對象),在析構(gòu)函數(shù)中調(diào)用delete來釋放堆對象.
C++11之后,主推三種智能指針:unique_ptr,shared_ptr,weak_ptr.智能指針的出現(xiàn)很大程度上避免了內(nèi)存泄漏這一個問題,不用重復使用new,delete來實現(xiàn)內(nèi)存的獲取和釋放;功能的實現(xiàn)主要是智能指針是一個類,當該類超出了類的作用域之后,會自動調(diào)用析構(gòu)函數(shù),析構(gòu)函數(shù)會自動釋放資源.
- unique_ptr是通過指針占有并管理另一個對象,在unique_ptr離開作用域時釋放該對象; unique_ptr對象通過運算符*和 ->(對于單個對象)或operator [](對于數(shù)組對象)提供對其管理對象的訪問來實現(xiàn)普通指針功能;且獨享被管理對象,同一時刻只能有一個unique_ptr擁有對象的所有權(quán),當其被賦值時對象的所有權(quán)也發(fā)生轉(zhuǎn)移备蚓,當其被銷毀時被管理對象也自動被銷毀.
- shared_ptr是通過指針保持對象共享所有權(quán)的智能指針,多個shared_ptr對象可以占有同一個對象;它使用計數(shù)機制來表明資源被多少個指針所共享,可以通過其成員函數(shù)use_count()來查看;多個線程能在shared_ptr不同實例上調(diào)用成員函數(shù)而不附加同步,即使這些實例是副本,且共享同一個對象的所有權(quán).但當多個線程訪問同一個shared_ptr而不設定同步,切任一線程使用shared_ptr的非const成員函數(shù)時,將出現(xiàn)數(shù)據(jù)競爭,解決方法是使用原子操作.還有一個問題是循環(huán)引用的問題:
如下例:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
std::shared_ptr<B> b;
A(){
cout << "hhyA" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B
{
public:
shared_ptr<A> a;
B()
{
cout << "hhyB" << endl;
};
~B()
{
cout << "~B()" << endl;
}
};
void test_shared_ptr_cycleRef()
{
shared_ptr<A>hhyA(new A);
shared_ptr<B>hhyB(new B);
hhyA->b = hhyB;
hhyB->a = hhyA;
cout << hhyA->b.use_count() << endl;
cout << hhyB->a.use_count() << endl;
}
int main()
{
test_shared_ptr_cycleRef();
return 0;
}
運行結(jié)果:
hhyA
hhyB
2
2
運行結(jié)果并沒有調(diào)用相應的析構(gòu)函數(shù),原因在于shared_ptr不僅管理著hhyA與hhyB,而且也管理著hhyA中的b,hhyB中的a;當銷毀時hhyA由hhyB中的a管理,hhyB由hhyA中的b管理,也就是說hhyA的釋放取決于hhyB,hhyB的釋放取決于hhyA,相互影響,造成了循環(huán)引用.
- weak_ptr是對被shared_ptr管理的對象的非擁有性("弱")引用,在其訪問引用的對象前必須先轉(zhuǎn)換為shared_ptr,目的是確定對象是否還存在;weak_ptr用來表達臨時所有權(quán)的概念:當某個對象只有存在時才需要被訪問,而且隨時可能被人刪除時,可以引用weak_ptr來跟蹤該對象;weak_ptr除了解決類之間循環(huán)引用問題,另一用法是打斷shared_ptr所管理對象的環(huán)狀引用問題,可以令該循環(huán)引用中的指針為弱指針以避免此情況.
解決類之間的循環(huán)引用的運行實例:
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
public:
// std::shared_ptr<B>b;
std::weak_ptr<B> b;
A(){
cout << "hhyA" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B
{
public:
// shared_ptr<A> a;
weak_ptr<A> a;
B()
{
cout << "hhyB" << endl;
};
~B()
{
cout << "~B()" << endl;
}
};
void test_shared_ptr_cycleRef()
{
shared_ptr<A>hhyA(new A);
shared_ptr<B>hhyB(new B);
hhyA->b = hhyB;
hhyB->a = hhyA;
cout << hhyA->b.use_count() << endl;
cout << hhyB->a.use_count() << endl;
}
int main()
{
test_shared_ptr_cycleRef();
return 0;
}
運行結(jié)果:
hhyA
hhyB
1
1
~B()
~A()
注意:
weak_ptr的使用必須結(jié)合shared_ptr,不能單獨使用;
注意析構(gòu)函數(shù)的順序,誰是weak_ptr對象,就先析構(gòu)誰;
weak_ptr之所以能夠解決循環(huán)引用問題就是在于其不增加也不減少循環(huán)引用次數(shù).
shared_ptr初始化時除了使用new更好的方法是使用make_shared:
如: auto foo = std::make_shared<int>(10);
自己實現(xiàn)一個智能指針(shared_ptr)
- 智能指針并不是真正的指針,是對一個真正指針的包裝,代理原指針.
- 在智能指針類中的析構(gòu)函數(shù)中,可以對原指針進行delete以及賦空指針的操作,在出了作用域之后會自動調(diào)用析構(gòu)函數(shù),
- 由于實現(xiàn)的shared_ptr,采用計數(shù)的操作來計算指向?qū)ο蟮闹羔槀€數(shù),當多個指針指向同一對象時,最后一個銷毀的智能指針(即計數(shù)器等于1的指針)需要對原指針進行銷毀.
- 需要拷貝構(gòu)造函數(shù)與拷貝賦值構(gòu)造函數(shù)
#ifndef THIS_SMART_PTR_H
#define THIS_SMART_PTR_H
#include <iostream>
#include <string>
using namespace std;
template <class T>
class smart_ptr
{
public:
//構(gòu)造函數(shù)
smart_ptr(T* p);
//拷貝構(gòu)造函數(shù)
smart_ptr(const smart_ptr &p);
//析構(gòu)函數(shù)
~smart_ptr();
//use_count
int use_count();
//賦值操作
smart_ptr& operator=(const smart_ptr &p);
//指針操作
T& operator*();
private:
T* ptr;
int* p_cnt;
};
template <class T>
smart_ptr<T>::smart_ptr(T* p):ptr(p)
{
if(ptr)
{
p_cnt = new int(1);
}
else
p_cnt = new int(0);
}
template <class T>
smart_ptr<T>::smart_ptr(const smart_ptr &p) {
if( this != &p){
ptr = p.ptr;
p_cnt = p.p_cnt;
(*p_cnt)++; //必須加括號,優(yōu)先級符號問題
}
}
template <class T>
smart_ptr<T>::~smart_ptr()
{
--(*p_cnt); //注意--不能放在后面
//只對最后一個對象引用ptr時才釋放
if(*p_cnt == 0)
{
delete ptr;
delete p_cnt;
ptr= nullptr;
p_cnt = nullptr;
cout << "final ~smart_ptr" << endl;
}
cout << "~smart_ptr" << endl;
}
template <class T>
int smart_ptr<T>::use_count() {
return *(this->p_cnt);
}
//重載等號函數(shù)不同于復制構(gòu)造函數(shù),即等號左邊的對象可能已經(jīng)指向某塊內(nèi)存,這樣,我們就得先判斷左邊對象指向的內(nèi)存已經(jīng)被引用的次數(shù).如果次數(shù)為1,表明我們可以釋放這塊內(nèi)存蝇狼;反之則不釋放珍语,由其他對象來釋放
template <class T>
smart_ptr<T>& smart_ptr<T>::operator=(const smart_ptr &p) {
if(this->ptr==p.ptr)
{
return *this; //檢測自我賦值
};
if(ptr != p.ptr)
{
--(*p_cnt);
// 將左操作數(shù)對象的使用計數(shù)減1,若該對象的使用計數(shù)減至0,則刪除該對象
if(*(this->p_cnt)==0)
{
delete ptr;
delete p_cnt;
}
}
ptr=p.ptr;
p_cnt=p.p_cnt;
++(*p_cnt);
return *this;
}
template<class T>
T& smart_ptr<T>::operator *()
{
return *ptr;
}
對代碼進行測試:
void test_smartptr()
{
smart_ptr<int>hhyA(new int(10));
cout << "hhyA.cnt" << hhyA.use_count() << endl;
smart_ptr<int>hhyB(hhyA); //拷貝構(gòu)造
cout << "hhyB.cnt" << hhyA.use_count() << endl;
smart_ptr<int>hhyC(new int(5));
cout << "hhyC.cnt" << hhyC.use_count() << endl;
hhyC = hhyB; //拷貝賦值
cout << "hhyC.cnt" << hhyC.use_count() << endl;
cout << *hhyA << endl;
}
int main()
{
test_smartptr();
return 0;
}
運行結(jié)果:
hhyA.cnt1
hhyB.cnt2
hhyC.cnt1
hhyC.cnt3
10
~smart_ptr
~smart_ptr
cout << "final ~smart_ptr" << endl;
~smart_ptr
結(jié)果分析:hhyA首先通過構(gòu)造函數(shù)使得*p_cnt等于1,然后hhyB通過拷貝構(gòu)造函數(shù)與hhyA指向同一對象,p_cnt的值又加一,再然后hhyC通過先指向另外一個對象,然后通過賦值構(gòu)造函數(shù),p_cnt的值先減一后再等于hhyA中的p_cnt的值(2)之后,再加一,即值最后等于三.
另外,調(diào)用析構(gòu)函數(shù)也是調(diào)用三次,但只有最后一個銷毀的智能指針會對原指針對象進行銷毀.