C++中智能指針如何設(shè)計(jì)和使用
更新時(shí)間:2012年11月28日 11:49:50 作者:
智能指針(smart pointer)是存儲(chǔ)指向動(dòng)態(tài)分配(堆)對(duì)象指針的類,用于生存期控制,能夠確保自動(dòng)正確的銷毀動(dòng)態(tài)分配的對(duì)象,防止內(nèi)存泄露,需要的朋友可以參考下
智能指針(smart pointer)是存儲(chǔ)指向動(dòng)態(tài)分配(堆)對(duì)象指針的類,用于生存期控制,能夠確保自動(dòng)正確的銷毀動(dòng)態(tài)分配的對(duì)象,防止內(nèi)存泄露。它的一種通用實(shí)現(xiàn)技術(shù)是使用引用計(jì)數(shù)(reference count)。智能指針類將一個(gè)計(jì)數(shù)器與類指向的對(duì)象相關(guān)聯(lián),引用計(jì)數(shù)跟蹤該類有多少個(gè)對(duì)象共享同一指針。每次創(chuàng)建類的新對(duì)象時(shí),初始化指針并將引用計(jì)數(shù)置為1;當(dāng)對(duì)象作為另一對(duì)象的副本而創(chuàng)建時(shí),拷貝構(gòu)造函數(shù)拷貝指針并增加與之相應(yīng)的引用計(jì)數(shù);對(duì)一個(gè)對(duì)象進(jìn)行賦值時(shí),賦值操作符減少左操作數(shù)所指對(duì)象的引用計(jì)數(shù)(如果引用計(jì)數(shù)為減至0,則刪除對(duì)象),并增加右操作數(shù)所指對(duì)象的引用計(jì)數(shù);調(diào)用析構(gòu)函數(shù)時(shí),構(gòu)造函數(shù)減少引用計(jì)數(shù)(如果引用計(jì)數(shù)減至0,則刪除基礎(chǔ)對(duì)象)。
智能指針就是模擬指針動(dòng)作的類。所有的智能指針都會(huì)重載 -> 和 * 操作符。智能指針還有許多其他功能,比較有用的是自動(dòng)銷毀。這主要是利用棧對(duì)象的有限作用域以及臨時(shí)對(duì)象(有限作用域?qū)崿F(xiàn))析構(gòu)函數(shù)釋放內(nèi)存。當(dāng)然,智能指針還不止這些,還包括復(fù)制時(shí)可以修改源對(duì)象等。智能指針根據(jù)需求不同,設(shè)計(jì)也不同(寫時(shí)復(fù)制,賦值即釋放對(duì)象擁有權(quán)限、引用計(jì)數(shù)等,控制權(quán)轉(zhuǎn)移等)。auto_ptr 即是一種常見的智能指針。
智能指針通常用類模板實(shí)現(xiàn):
template <class T>
class smartpointer
{
private:
T *_ptr;
public:
smartpointer(T *p) : _ptr(p) //構(gòu)造函數(shù)
{
}
T& operator *() //重載*操作符
{
return *_ptr;
}
T* operator ->() //重載->操作符
{
return _ptr;
}
~smartpointer() //析構(gòu)函數(shù)
{
delete _ptr;
}
};
實(shí)現(xiàn)引用計(jì)數(shù)有兩種經(jīng)典策略,在這里將使用其中一種,這里所用的方法中,需要定義一個(gè)單獨(dú)的具體類用以封裝引用計(jì)數(shù)和相關(guān)指針:
// 定義僅由HasPtr類使用的U_Ptr類,用于封裝使用計(jì)數(shù)和相關(guān)指針
// 這個(gè)類的所有成員都是private,我們不希望普通用戶使用U_Ptr類,所以它沒有任何public成員
// 將HasPtr類設(shè)置為友元,使其成員可以訪問U_Ptr的成員
class U_Ptr
{
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p) : ip(p) , use(1)
{
cout << "U_ptr constructor called !" << endl;
}
~U_Ptr()
{
delete ip;
cout << "U_ptr distructor called !" << endl;
}
};
HasPtr類需要一個(gè)析構(gòu)函數(shù)來刪除指針。但是,析構(gòu)函數(shù)不能無條件的刪除指針?!?BR> 條件就是引用計(jì)數(shù)。如果該對(duì)象被兩個(gè)指針?biāo)福敲磩h除其中一個(gè)指針,并不會(huì)調(diào)用該指針的析構(gòu)函數(shù),因?yàn)榇藭r(shí)還有另外一個(gè)指針指向該對(duì)象??磥?,智能指針主要是預(yù)防不當(dāng)?shù)奈鰳?gòu)行為,防止出現(xiàn)懸垂指針。

如上圖所示,HasPtr就是智能指針,U_Ptr為計(jì)數(shù)器;里面有個(gè)變量use和指針ip,use記錄了*ip對(duì)象被多少個(gè)HasPtr對(duì)象所指。假設(shè)現(xiàn)在又兩個(gè)HasPtr對(duì)象p1、p2指向了U_Ptr,那么現(xiàn)在我delete p1,use變量將自減1, U_Ptr不會(huì)析構(gòu),那么U_Ptr指向的對(duì)象也不會(huì)析構(gòu),那么p2仍然指向了原來的對(duì)象,而不會(huì)變成一個(gè)懸空指針。當(dāng)delete p2的時(shí)候,use變量將自減1,為0。此時(shí),U_Ptr對(duì)象進(jìn)行析構(gòu),那么U_Ptr指向的對(duì)象也進(jìn)行析構(gòu),保證不會(huì)出現(xiàn)內(nèi)存泄露。
包含指針的類需要特別注意復(fù)制控制,原因是復(fù)制指針時(shí)只復(fù)制指針中的地址,而不會(huì)復(fù)制指針指向的對(duì)象。
大多數(shù)C++類用三種方法之一管理指針成員
(1)不管指針成員。復(fù)制時(shí)只復(fù)制指針,不復(fù)制指針指向的對(duì)象。當(dāng)其中一個(gè)指針把其指向的對(duì)象的空間釋放后,其它指針都成了懸浮指針。這是一種極端
(2)當(dāng)復(fù)制的時(shí)候,即復(fù)制指針,也復(fù)制指針指向的對(duì)象。這樣可能造成空間的浪費(fèi)。因?yàn)橹羔樦赶虻膶?duì)象的復(fù)制不一定是必要的。
(3) 第三種就是一種折中的方式。利用一個(gè)輔助類來管理指針的復(fù)制。原來的類中有一個(gè)指針指向輔助類,輔助類的數(shù)據(jù)成員是一個(gè)計(jì)數(shù)器和一個(gè)指針(指向原來的)(此為本次智能指針實(shí)現(xiàn)方式)。
其實(shí),智能指針的引用計(jì)數(shù)類似于java的垃圾回收機(jī)制:java的垃圾的判定很簡答,如果一個(gè)對(duì)象沒有引用所指,那么該對(duì)象為垃圾。系統(tǒng)就可以回收了。
HasPtr 智能指針的聲明如下,保存一個(gè)指向U_Ptr對(duì)象的指針,U_Ptr對(duì)象指向?qū)嶋H的int基礎(chǔ)對(duì)象,代碼如下:
#include<iostream>
using namespace std;
// 定義僅由HasPtr類使用的U_Ptr類,用于封裝使用計(jì)數(shù)和相關(guān)指針
// 這個(gè)類的所有成員都是private,我們不希望普通用戶使用U_Ptr類,所以它沒有任何public成員
// 將HasPtr類設(shè)置為友元,使其成員可以訪問U_Ptr的成員
class U_Ptr
{
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p) : ip(p) , use(1)
{
cout << "U_ptr constructor called !" << endl;
}
~U_Ptr()
{
delete ip;
cout << "U_ptr distructor called !" << endl;
}
};
class HasPtr
{
public:
// 構(gòu)造函數(shù):p是指向已經(jīng)動(dòng)態(tài)創(chuàng)建的int對(duì)象指針
HasPtr(int *p, int i) : ptr(new U_Ptr(p)) , val(i)
{
cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl;
}
// 復(fù)制構(gòu)造函數(shù):復(fù)制成員并將使用計(jì)數(shù)加1
HasPtr(const HasPtr& orig) : ptr(orig.ptr) , val(orig.val)
{
++ptr->use;
cout << "HasPtr copy constructor called ! " << "use = " << ptr->use << endl;
}
// 賦值操作符
HasPtr& operator=(const HasPtr&);
// 析構(gòu)函數(shù):如果計(jì)數(shù)為0,則刪除U_Ptr對(duì)象
~HasPtr()
{
cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl;
if (--ptr->use == 0)
delete ptr;
}
// 獲取數(shù)據(jù)成員
int *get_ptr() const
{
return ptr->ip;
}
int get_int() const
{
return val;
}
// 修改數(shù)據(jù)成員
void set_ptr(int *p) const
{
ptr->ip = p;
}
void set_int(int i)
{
val = i;
}
// 返回或修改基礎(chǔ)int對(duì)象
int get_ptr_val() const
{
return *ptr->ip;
}
void set_ptr_val(int i)
{
*ptr->ip = i;
}
private:
U_Ptr *ptr; //指向使用計(jì)數(shù)類U_Ptr
int val;
};
HasPtr& HasPtr::operator = (const HasPtr &rhs) //注意,這里賦值操作符在減少做操作數(shù)的使用計(jì)數(shù)之前使rhs的使用技術(shù)加1,從而防止自我賦值
{
// 增加右操作數(shù)中的使用計(jì)數(shù)
++rhs.ptr->use;
// 將左操作數(shù)對(duì)象的使用計(jì)數(shù)減1,若該對(duì)象的使用計(jì)數(shù)減至0,則刪除該對(duì)象
if (--ptr->use == 0)
delete ptr;
ptr = rhs.ptr; // 復(fù)制U_Ptr指針
val = rhs.val; // 復(fù)制int成員
return *this;
}
int main(void)
{
int *pi = new int(42);
HasPtr *hpa = new HasPtr(pi, 100); // 構(gòu)造函數(shù)
HasPtr *hpb = new HasPtr(*hpa); // 拷貝構(gòu)造函數(shù)
HasPtr *hpc = new HasPtr(*hpb); // 拷貝構(gòu)造函數(shù)
HasPtr hpd = *hpa; // 拷貝構(gòu)造函數(shù)
cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
hpc->set_ptr_val(10000);
cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
hpd.set_ptr_val(10);
cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
delete hpa;
delete hpb;
delete hpc;
cout << hpd.get_ptr_val() << endl;
return 0;
}
這里的賦值操作符比較麻煩,且讓我用圖表分析一番:
假設(shè)現(xiàn)在又兩個(gè)智能指針p1、 p2,一個(gè)指向內(nèi)容為42的內(nèi)存,一個(gè)指向內(nèi)容為100的內(nèi)存,如下圖:

現(xiàn)在,我要做賦值操作,p2 = p1。對(duì)比著上面的
此時(shí),條件成立。因?yàn)閡2的use為1。那么,運(yùn)行U_Ptr的析構(gòu)函數(shù),而在U_Ptr的析構(gòu)函數(shù)中,做了delete ip操作,所以釋放了內(nèi)存,不會(huì)有內(nèi)存泄露的問題。
接下來的操作很自然,無需多言:

而還要注意的是,重載賦值操作符的時(shí)候,一定要注意的是,檢查自我賦值的情況。
如圖所示:

此時(shí),做p1 = p1的操作。那么,首先u1.use自增1,為2;然后,u1.use自減1,為1。那么就不會(huì)執(zhí)行delete操作,剩下的操作都可以順利進(jìn)行。按《C++ primer》說法,“這個(gè)賦值操作符在減少左操作數(shù)的使用計(jì)數(shù)之前使rhs的使用計(jì)數(shù)加1,從而防止自身賦值”。哎,反正我是那樣理解的。當(dāng)然,賦值操作符函數(shù)中一來就可以按常規(guī)那樣:

智能指針就是模擬指針動(dòng)作的類。所有的智能指針都會(huì)重載 -> 和 * 操作符。智能指針還有許多其他功能,比較有用的是自動(dòng)銷毀。這主要是利用棧對(duì)象的有限作用域以及臨時(shí)對(duì)象(有限作用域?qū)崿F(xiàn))析構(gòu)函數(shù)釋放內(nèi)存。當(dāng)然,智能指針還不止這些,還包括復(fù)制時(shí)可以修改源對(duì)象等。智能指針根據(jù)需求不同,設(shè)計(jì)也不同(寫時(shí)復(fù)制,賦值即釋放對(duì)象擁有權(quán)限、引用計(jì)數(shù)等,控制權(quán)轉(zhuǎn)移等)。auto_ptr 即是一種常見的智能指針。
智能指針通常用類模板實(shí)現(xiàn):
復(fù)制代碼 代碼如下:
template <class T>
class smartpointer
{
private:
T *_ptr;
public:
smartpointer(T *p) : _ptr(p) //構(gòu)造函數(shù)
{
}
T& operator *() //重載*操作符
{
return *_ptr;
}
T* operator ->() //重載->操作符
{
return _ptr;
}
~smartpointer() //析構(gòu)函數(shù)
{
delete _ptr;
}
};
實(shí)現(xiàn)引用計(jì)數(shù)有兩種經(jīng)典策略,在這里將使用其中一種,這里所用的方法中,需要定義一個(gè)單獨(dú)的具體類用以封裝引用計(jì)數(shù)和相關(guān)指針:
復(fù)制代碼 代碼如下:
// 定義僅由HasPtr類使用的U_Ptr類,用于封裝使用計(jì)數(shù)和相關(guān)指針
// 這個(gè)類的所有成員都是private,我們不希望普通用戶使用U_Ptr類,所以它沒有任何public成員
// 將HasPtr類設(shè)置為友元,使其成員可以訪問U_Ptr的成員
class U_Ptr
{
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p) : ip(p) , use(1)
{
cout << "U_ptr constructor called !" << endl;
}
~U_Ptr()
{
delete ip;
cout << "U_ptr distructor called !" << endl;
}
};
HasPtr類需要一個(gè)析構(gòu)函數(shù)來刪除指針。但是,析構(gòu)函數(shù)不能無條件的刪除指針?!?BR> 條件就是引用計(jì)數(shù)。如果該對(duì)象被兩個(gè)指針?biāo)福敲磩h除其中一個(gè)指針,并不會(huì)調(diào)用該指針的析構(gòu)函數(shù),因?yàn)榇藭r(shí)還有另外一個(gè)指針指向該對(duì)象??磥?,智能指針主要是預(yù)防不當(dāng)?shù)奈鰳?gòu)行為,防止出現(xiàn)懸垂指針。

如上圖所示,HasPtr就是智能指針,U_Ptr為計(jì)數(shù)器;里面有個(gè)變量use和指針ip,use記錄了*ip對(duì)象被多少個(gè)HasPtr對(duì)象所指。假設(shè)現(xiàn)在又兩個(gè)HasPtr對(duì)象p1、p2指向了U_Ptr,那么現(xiàn)在我delete p1,use變量將自減1, U_Ptr不會(huì)析構(gòu),那么U_Ptr指向的對(duì)象也不會(huì)析構(gòu),那么p2仍然指向了原來的對(duì)象,而不會(huì)變成一個(gè)懸空指針。當(dāng)delete p2的時(shí)候,use變量將自減1,為0。此時(shí),U_Ptr對(duì)象進(jìn)行析構(gòu),那么U_Ptr指向的對(duì)象也進(jìn)行析構(gòu),保證不會(huì)出現(xiàn)內(nèi)存泄露。
包含指針的類需要特別注意復(fù)制控制,原因是復(fù)制指針時(shí)只復(fù)制指針中的地址,而不會(huì)復(fù)制指針指向的對(duì)象。
大多數(shù)C++類用三種方法之一管理指針成員
(1)不管指針成員。復(fù)制時(shí)只復(fù)制指針,不復(fù)制指針指向的對(duì)象。當(dāng)其中一個(gè)指針把其指向的對(duì)象的空間釋放后,其它指針都成了懸浮指針。這是一種極端
(2)當(dāng)復(fù)制的時(shí)候,即復(fù)制指針,也復(fù)制指針指向的對(duì)象。這樣可能造成空間的浪費(fèi)。因?yàn)橹羔樦赶虻膶?duì)象的復(fù)制不一定是必要的。
(3) 第三種就是一種折中的方式。利用一個(gè)輔助類來管理指針的復(fù)制。原來的類中有一個(gè)指針指向輔助類,輔助類的數(shù)據(jù)成員是一個(gè)計(jì)數(shù)器和一個(gè)指針(指向原來的)(此為本次智能指針實(shí)現(xiàn)方式)。
其實(shí),智能指針的引用計(jì)數(shù)類似于java的垃圾回收機(jī)制:java的垃圾的判定很簡答,如果一個(gè)對(duì)象沒有引用所指,那么該對(duì)象為垃圾。系統(tǒng)就可以回收了。
HasPtr 智能指針的聲明如下,保存一個(gè)指向U_Ptr對(duì)象的指針,U_Ptr對(duì)象指向?qū)嶋H的int基礎(chǔ)對(duì)象,代碼如下:
復(fù)制代碼 代碼如下:
#include<iostream>
using namespace std;
// 定義僅由HasPtr類使用的U_Ptr類,用于封裝使用計(jì)數(shù)和相關(guān)指針
// 這個(gè)類的所有成員都是private,我們不希望普通用戶使用U_Ptr類,所以它沒有任何public成員
// 將HasPtr類設(shè)置為友元,使其成員可以訪問U_Ptr的成員
class U_Ptr
{
friend class HasPtr;
int *ip;
size_t use;
U_Ptr(int *p) : ip(p) , use(1)
{
cout << "U_ptr constructor called !" << endl;
}
~U_Ptr()
{
delete ip;
cout << "U_ptr distructor called !" << endl;
}
};
class HasPtr
{
public:
// 構(gòu)造函數(shù):p是指向已經(jīng)動(dòng)態(tài)創(chuàng)建的int對(duì)象指針
HasPtr(int *p, int i) : ptr(new U_Ptr(p)) , val(i)
{
cout << "HasPtr constructor called ! " << "use = " << ptr->use << endl;
}
// 復(fù)制構(gòu)造函數(shù):復(fù)制成員并將使用計(jì)數(shù)加1
HasPtr(const HasPtr& orig) : ptr(orig.ptr) , val(orig.val)
{
++ptr->use;
cout << "HasPtr copy constructor called ! " << "use = " << ptr->use << endl;
}
// 賦值操作符
HasPtr& operator=(const HasPtr&);
// 析構(gòu)函數(shù):如果計(jì)數(shù)為0,則刪除U_Ptr對(duì)象
~HasPtr()
{
cout << "HasPtr distructor called ! " << "use = " << ptr->use << endl;
if (--ptr->use == 0)
delete ptr;
}
// 獲取數(shù)據(jù)成員
int *get_ptr() const
{
return ptr->ip;
}
int get_int() const
{
return val;
}
// 修改數(shù)據(jù)成員
void set_ptr(int *p) const
{
ptr->ip = p;
}
void set_int(int i)
{
val = i;
}
// 返回或修改基礎(chǔ)int對(duì)象
int get_ptr_val() const
{
return *ptr->ip;
}
void set_ptr_val(int i)
{
*ptr->ip = i;
}
private:
U_Ptr *ptr; //指向使用計(jì)數(shù)類U_Ptr
int val;
};
HasPtr& HasPtr::operator = (const HasPtr &rhs) //注意,這里賦值操作符在減少做操作數(shù)的使用計(jì)數(shù)之前使rhs的使用技術(shù)加1,從而防止自我賦值
{
// 增加右操作數(shù)中的使用計(jì)數(shù)
++rhs.ptr->use;
// 將左操作數(shù)對(duì)象的使用計(jì)數(shù)減1,若該對(duì)象的使用計(jì)數(shù)減至0,則刪除該對(duì)象
if (--ptr->use == 0)
delete ptr;
ptr = rhs.ptr; // 復(fù)制U_Ptr指針
val = rhs.val; // 復(fù)制int成員
return *this;
}
int main(void)
{
int *pi = new int(42);
HasPtr *hpa = new HasPtr(pi, 100); // 構(gòu)造函數(shù)
HasPtr *hpb = new HasPtr(*hpa); // 拷貝構(gòu)造函數(shù)
HasPtr *hpc = new HasPtr(*hpb); // 拷貝構(gòu)造函數(shù)
HasPtr hpd = *hpa; // 拷貝構(gòu)造函數(shù)
cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
hpc->set_ptr_val(10000);
cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
hpd.set_ptr_val(10);
cout << hpa->get_ptr_val() << " " << hpb->get_ptr_val() << endl;
delete hpa;
delete hpb;
delete hpc;
cout << hpd.get_ptr_val() << endl;
return 0;
}
這里的賦值操作符比較麻煩,且讓我用圖表分析一番:
假設(shè)現(xiàn)在又兩個(gè)智能指針p1、 p2,一個(gè)指向內(nèi)容為42的內(nèi)存,一個(gè)指向內(nèi)容為100的內(nèi)存,如下圖:

現(xiàn)在,我要做賦值操作,p2 = p1。對(duì)比著上面的
此時(shí),rhs就是p1,首先將p1指向的ptr的use加1,復(fù)制代碼 代碼如下:
HasPtr& operator=(const HasPtr&); // 賦值操作符
然后,做:復(fù)制代碼 代碼如下:
++rhs.ptr->use; // 增加右操作數(shù)中的使用計(jì)數(shù)
因?yàn)?,原先p2指向的對(duì)象現(xiàn)在p2不在指向,那么該對(duì)象就少了一個(gè)指針去指,所以,use做自減1;復(fù)制代碼 代碼如下:
if (--ptr->use == 0)
delete ptr;
此時(shí),條件成立。因?yàn)閡2的use為1。那么,運(yùn)行U_Ptr的析構(gòu)函數(shù),而在U_Ptr的析構(gòu)函數(shù)中,做了delete ip操作,所以釋放了內(nèi)存,不會(huì)有內(nèi)存泄露的問題。
接下來的操作很自然,無需多言:
做完賦值操作后,那么就成為如下圖所示了。紅色標(biāo)注的就是變化的部分:復(fù)制代碼 代碼如下:
ptr = rhs.ptr; // 復(fù)制U_Ptr指針
val = rhs.val; // 復(fù)制int成員
return *this;

而還要注意的是,重載賦值操作符的時(shí)候,一定要注意的是,檢查自我賦值的情況。
如圖所示:

此時(shí),做p1 = p1的操作。那么,首先u1.use自增1,為2;然后,u1.use自減1,為1。那么就不會(huì)執(zhí)行delete操作,剩下的操作都可以順利進(jìn)行。按《C++ primer》說法,“這個(gè)賦值操作符在減少左操作數(shù)的使用計(jì)數(shù)之前使rhs的使用計(jì)數(shù)加1,從而防止自身賦值”。哎,反正我是那樣理解的。當(dāng)然,賦值操作符函數(shù)中一來就可以按常規(guī)那樣:
運(yùn)行結(jié)果如下圖:復(fù)制代碼 代碼如下:
if(this == &rhs)
return *this;

相關(guān)文章
C語言實(shí)現(xiàn)靜態(tài)版通訊錄的示例代碼
這篇文章主要為大家詳細(xì)介紹了如何利用C語言實(shí)現(xiàn)一個(gè)簡單的靜態(tài)版通訊錄,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語言有一定幫助,需要的可以參考一下2022-08-08C語言中返回錯(cuò)誤信息的相關(guān)函數(shù)用法總結(jié)
這篇文章主要介紹了C語言中返回錯(cuò)誤信息的相關(guān)函數(shù)用法總結(jié),包括strerror()函數(shù)和perror()函數(shù)以及ferror()函數(shù)的使用,需要的朋友可以參考下2015-09-09