很久以前就對基於引用計數的指針指針很感興趣,今天突然又一次想到這個問題,所以就寫了一個基於引用技術的智能指針。該智能指針的具體實現方式如下:
1#include
<iostream>
2#include
<numeric>
3#include
<algorithm>
4#include
<map>
5#include
<assert.h>
6
8
9using namespace std;
10
11template<typename
T>
12class SmartPointByRefCount
13{
14public:
15
SmartPointByRefCount(T* pT) : m_pT(pT)
16 {
17
SmartPointByRefCount::AddRefCount((unsigned int)m_pT);
18
}
19
20
SmartPointByRefCount(const SmartPointByRefCount& ref)
21 {
22
SmartPointByRefCount::AddRefCount((unsigned int)(ref.m_pT));
23
m_pT = ref.m_pT;
24
}
25
26
SmartPointByRefCount& operator=(const SmartPointByRefCount& ref)
27 {
28 if (this !=
&ref)
29 {
30
SmartPointByRefCount::AddRefCount(ref.m_pT);
31
m_pT = ref.m_pT;
32
}
33
34 return *this;
35
}
36
37
~SmartPointByRefCount()
38 {
39 if (SmartPointByRefCount::IsShouldDelete((unsigned int)m_pT))
40 {
41
SmartPointByRefCount::DelRefCount((unsigned int)m_pT);
42
delete m_pT;
43
}
44 else
45 {
46
SmartPointByRefCount::RedRefCount((unsigned int)m_pT);
47
}
48
}
49
50
T* operator->()
51 {
52 return m_pT;
53
}
54
55
T& operator*()
56 {
57 return *m_pT;
58
}
59
60private:
61
T *m_pT;
62
63private:
64 static std::map<unsigned int,
unsigned int> m_mapUseCount;
65
66public:
67 static bool IsShouldDelete(unsigned int);
68 static void AddRefCount(unsigned int);
69 static void RedRefCount(unsigned int);
70 static void DelRefCount(unsigned int);
71};
72
73
74template<typename
T> std::map<unsigned int, unsigned int> SmartPointByRefCount<T>::m_mapUseCount;
75
76template<typename
T>
77bool SmartPointByRefCount<T>::IsShouldDelete(unsigned int nPointer)
78{
79
std::map<unsigned int, unsigned int>::const_iterator it = m_mapUseCount.find(nPointer);
80 if (it
!= m_mapUseCount.end())
81 {
82 return (
1 == it->second );
83
}
84 else
85 {
86
assert(false);
87 return false;
88
}
89}
90
91template<typename
T>
92void SmartPointByRefCount<T>::AddRefCount(unsigned int nPointer)
93{
94
std::map<unsigned int, unsigned int>::iterator it = m_mapUseCount.find(nPointer);
95 if (it
!= m_mapUseCount.end())
96 {
97
(it->second)++;
98
}
99 else
100 {
101
m_mapUseCount[nPointer] = 1;
102
}
103}
104
105template<typename
T>
106void SmartPointByRefCount<T>::RedRefCount(unsigned int nPointer)
107{
108
std::map<unsigned int, unsigned int>::iterator it = m_mapUseCount.find(nPointer);
109 if (it
!= m_mapUseCount.end() && 1 < it->second)
110 {
111
(it->second)--;
112
}
113 else
114 {
115
assert(false);
116
}
117}
118
119template<typename
T>
120void SmartPointByRefCount<T>::DelRefCount(unsigned int nPointer)
121{
122
std::map<unsigned int, unsigned int>::iterator it = m_mapUseCount.find(nPointer);
123 if (it
!= m_mapUseCount.end())
124 {
125
m_mapUseCount.erase(it);
126
}
127 else
128 {
129
assert(false);
130
}
131}
以下是測試代碼,該智能指針能夠完全通過這些測試代碼。
1SmartPointByRefCount<int>
TestSPR(SmartPointByRefCount<int> dd)
2{
3
*dd = 4;
4 return dd;
5}
6
7
8int main(int argc, char *argv)
9{
10
SmartPointByRefCount<int> spb1(new int(0));
11
SmartPointByRefCount<int> spb2 = TestSPR(spb1);
12
*spb1 = 2;
13
14
15 {
16
SmartPointByRefCount<int> spb3(spb2);
17
SmartPointByRefCount<int> spb4 = TestSPR(spb3);
18
}
19
20 return 0;
21}
寫完這個智能指針之後我在網上找了找其他人寫的智能指針,發現智能指針有兩種典型實現方式:侵入式、非侵入式,而我的實現方式是非侵入式的。以下兩篇文章是從網上引用的,從這兩篇文章中發現了一些問題,也學到了一些知識。以下引用內容的所有版權歸原作者所有。
第一篇:
一種自適應的引用計數智能指針的實現
0 引言
自行管理內存是C ++ 語言的一個重要特徵, 它爲開發人員充分、靈活利用內存空間提供了方 便,但同時也負擔了大量內存的請求、釋放工作.
程序的邏輯錯誤以及不確定的異常引發,通常會由於內存未釋放而造成系統的內存泄漏. 實際工 作證明,內存泄漏是最主要的C + + 程序Bug 之 一,由程序進行自動內存管理可有效地避免內存 泄漏,這也是提高C + + 程序質量的重要途經之 一.
自動內存管理通常的方式是引入垃圾回收 (Garbage Collection) 機制. 垃圾回收作爲動態語言 的特徵之一早已存在於多種程序設計語言中,如 Java、Python 等. 遺憾的是最新的C ++ 標準並沒有將其引入,同時,由於C ++ 語言是強類型的靜態語言,通過程序來模擬垃圾回收機制非常困難,且功能有限. 避開垃圾回收,通過運用C ++ 語言的運算符重載和範型機制,在C ++ 社區產生了稱爲智能指針(Smart Pointer) 的內存工具. 所謂智能指針,實際上是一種類模板,當其實例化後包含有指向動態分配內存的指針,它通過重載* 、->運算符來模擬指針的行爲,同時在適當的時候釋放內存,以達到自動內存管理的目的,其定義通常爲template
< typename T > class Smart Ptr. 現在,智能指針在C ++ 應用開發中大量使用, 已經成爲C ++ 標準的工具之一.
1 智能指針的一般實現和使用
通常,智能指針按使用的策略分爲兩類:一類是面向控制資源的所有權的智能指針,這類智能指針保證資源只被一個確定的對象使用,任何智能指針的拷貝或傳遞均會發生所有權的轉換,如標準庫提供的std : :auto ptr[1 ] ;另一類是面向資源共享的智能指針,這類智能指針內部通常存在使用資源的記數器,稱爲引用計數,它保證引用計數爲0 時(即不再有對象使用資源) 釋放資源, 如Boost 庫的boost : :shared ptr[2 ] . 智能指針能很好的完成內存自動釋放,而它
的使用與普通指針非常類似:
{
⋯⋯
//定義int 的智能指針si
boost : :shared ptr < int > si = new int (1) ;
//直接使用si
std::cout<<*si<<std : :endl ;
//定義string 的智能指針st
boost::shared-ptr<std::string > st = new std::string(″Foo″) ;
//通過- > 運算符訪問string 的成員函數
st->clear () ;
//qt 和st 訪問的是同一資源
boost : :shared ptr < std : :string > qt = st ;
qt - > append(″Bar″) ;
⋯⋯
//si st 中的指針將自動釋放
}
2 引用計數的實現
資源共享型的智能指針引用計數的設計,一般分侵入式與非侵入式兩種. 侵入式引用計數,將計數器作爲包含對象的一部分,該方法效率比較 高,但要求包含類型總是繼承自一個計數對象;非侵入式引用計數,計數器通過動態內存分配獲得,任何的對象均可作爲智能指針的範型參數,但小內存的new 操作會降低智能指針的效率. 關於效率和靈活的平衡,在智能指針的設計中是不能完成的,只有把這個問題交給智能指針的使用者. 但通過模板元編程以及模板偏特化,我們可以爲使用者提供自動識別包含類型的智能指針[3 ] . 通過模板元編程可在程序的編譯期間判斷一個類型是否繼承自計數對象,如果是則採用侵入式,否則採用非侵入式.侵入式的引用計數應繼承的基類:
class IntrusiveRefCount
{
//操作引用計數成員函數
⋯⋯
protected :
int refCount ;PPP< 引用計數
} ;
通過不同的方式實現智能指針,模板參數IntrRefCount 缺省爲True ,即採用侵入式:
template < typename T,bool IntrRefCount >
class SmartPtrImp
{
//重載賦值運算符.
//通過調用T的成員函數DecRef()和IncRef ( ) 來改變引用值
SmartPtrImp &operator = (const SmartPtrImp &sptr)
{
//釋放當前資源
if (pointee ! = 0 &&pointee - > DecRef ( ) == 0)
delete pointee ;
pointee = sptr.pointee ;
if (pointee ! = 0)
pointee->IncRef() ;
return *this ;
}
//拷貝構造函數. 同樣有引用值的操作
SmartPtrImp (const SmartPtrImp &sptr) ;
protected :
T *pointee ;///對象指針
};
採用模板偏特化參數IntrRefCount ( 特化爲false) ,此類模板採用非侵入式模式:
template < typename T>
class SmartPtrImp < T,false >
{
//重載賦值運算符.
//直接new int (0) 來獲取記數器
SmartPtrImp &operator = (T* ptr)
{
if (pointee ! = 0)
{
if (--(*refCountPtr ) == 0)
delete pointee ;
else
//失去了對原對象的控制,原對象的引用計數也要放棄
refCountPtr = new int (0) ;
}
pointee = ptr ;
if (pointee ! = 0)
++refCountPtr ;
return *this ;
}
//拷貝構造函數. 同樣有引用值的操作
SmartPtrImp (const SmartPtrImp &sptr) ;
protected :
T *pointee ;///對象指針
int *refCountPtr;///引用計數
} ;
3 智能指針的實現
現在我們需要一個算法判斷一個類是否是另一個類的子類的範型算法[4 ] ,實現如下:
//如果沒有看過陳偉柱翻譯的《C++ Template》的話下面的代碼可能很難看懂,如果看不懂的話,不妨找這本書看一下
template < typename D ,typename B >
struct IsDerivedFrom
{
class No {} ;
class Yes {No no[2 ] ;} ;
static Yes Test (B*) ;
static No Test ( ⋯) ;
enum {Is = sizeof (Test ( static cast <D*> (0) ) ) = = sizeof(Yes) } ;
} ;
如果類型D 是B 的子類IsDerivedFrom < D ,B>::Is 的值就爲1.
有了如上工具,我們就可以將智能指針的實現拼接起來:
template < typename T>
class SmartPtr :
public SmartPtrImp < T, IsDerivedFrom < T, IntrusiveRefCount>::Is == 1 >
{
typedef SmartPtrImp < T, IsDerivedFrom < T, IntrusiveRefCount>::Is == 1, Deletor < T > > ParentCls ;
public :
SmartPtr () :ParentCls () {}
explicit SmartPtr (T &t) :ParentCls (t) {}
explicit SmartPtr (T*t) :ParentCls (t) {}
SmartPtr (const SmartPtr &sptr) :ParentCls (sptr) {}
}
從以上的說明上看,這篇論文只有一點有價值的——講明白了智能指針的兩種實現方式,侵入式和非侵入式。至於這篇自適應的說法,個人感覺在編碼的時候根本不會用到。其中紅色的文字是我添加的。
第二篇:
指針 是C++中不得不談的一個話題,或許我還不是很能熟練的掌握指針以及我所要討論的引用計數型指針的全部,但是還是有那麼些迫不及待想要表達一下。
指針pointer 是資源泄漏 resource leak 的根源(當然可能還有其他一些什麼東西,在我的映像中 異常 彷彿也會造成資源泄漏)最簡單的一個資源泄漏的例子就是new和delete這樣的動態內存分配算子沒有正確使用造成的:
struct A
{
A() { printf("A Constructor!"); }
~A() { printf("A Destructor!"); }
};
void area()
{
A *p = new A();
}
執行完 area() 後,自然是隻有A構造的消息,而A的析構卻不見影蹤。這裏我們在離開了area作用域後,我們就無法對p所指向之資源進行操作,A的實例就會被懸掛在內存的某處得不到清理。一個形象點的比方就像人類發送的宇宙衛星失去了動力以及和地球的聯繫,無法收回,就變成了宇宙垃圾~。
然而利用對象來管理資源是一個很好的辦法,因爲對象的實例本身在脫離作用域後會自動清理,就像這樣
class A_holder
{
public:
expilict A_holder(A* p = NULL):ptr(p) {}
~A_holder()
{
if (ptr)
delete ptr;
}
private:
A* ptr;
};
如此,我們在area裏面把資源的管理權力交給A_holder,就像下面這樣
void area()
{
A_holder ah(new A);
}
這樣,ah在離開area後會自動調用其析構函數,就達到了自動管理該資源的目的。
利用C++的類的實例離開作用域會自動調用其析構函數的機制,可以比較方便的管理資源,但是在使用普通指針的情況下會存在多個指針指向同一對象的情況。
void multi_point()
{
int a;
int *p1,*p2;
p1 = &a;
p2 = &a;
}
實際的指針指向情況應該是這樣 p1 –>ß a à<- p2。
這裏就出現了一個問題,我們想取消p1的時候可能會出現兩種語義:
1、將p1和其指向的對象一起刪除,這樣p2也就不可以繼續對a進行使用。但是往往p2的使用者不會知道a已經刪除,則出現了錯誤。
2、將p1與其指向的對象解除關係,這樣p2還可以對a進行使用。
對於普通的delete操作,實現的是第一種情況,這樣通過p2對a進行訪問必然會造成致命的錯誤。
在實現holder類的時候應該也考慮到第二種情況,如果有另外一個holder也指向這個資源,其中一個holder銷燬,另外一個holder還可能會使用到它們共同指向的那個資源。於是,holder的作用就不僅僅是單單的持有和施放資源,還應該處理有多少個對其hold資源的引用(即引用計數),並且在引用計數降爲0時真正的銷燬資源實體。
如此,一個行爲類似指針(有->,*操作符)的智能指針出現,它管理賦予其資源的引用計數,也管理該資源的生死存亡。
一個簡單的Reference Count Smart Pointer的實現如下:
#ifndef COUNTED_PTR_HPP
#define COUNTED_PTR_HPP
/*class for counted reference semantics
*-deletes the object to which it refers when the last CountedPtr
* that refers to it is destroyed
*/
template <class T>
class CountedPtr
{
private:
T* ptr; // pointer to the value
long* count; // shared number of owners
public:
//initialize pointer with existing pointer
//-requires that the pointer p is a return value of new
explicit CountedPtr (T* p=0)
: ptr(p), count(new long(1)) {}
//copy pointer (one more owner)
CountedPtr (const CountedPtr<T>& p) throw()
: ptr(p.ptr), count(p.count)
{
++*count;
}
//destructor (delete value if this was the last owner)
~CountedPtr () throw()
{
dispose();
}
//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
if (this != &p) {
dispose();
ptr = p.ptr;
count = p.count;
++*count;
}
return *this;
}
//access the value to which the pointer refers
T& operator*() const throw() {
return *ptr;
}
T* operator->() const throw() {
return ptr;
}
private:
void dispose() {
if (--*count == 0) {
delete count;
delete ptr;
}
}
};
#endif /*COUNTED_PTR_HPP*/
由此,一個新的問題出現了!循環引用!
這樣的一個引用計數型智能指針目的是爲了防止資源泄漏,但是只需要一個很小巧的代碼就可以讓這樣的初衷化爲烏有……。
class A
{
public:
A() {cout<<"A CON"<<endl;}
~A() {cout<<"A DES"<<endl;}
void hold(CountedPtr<A> ptr)
{
m_ptr = ptr;
}
private:
CountedPtr<A> m_ptr;
};
void self_cir_area()
{
CountedPtr<A> pA(new A());
pA->hold(pA);
}
可以看見,一個對象A中有一個引用計數型智能指針,這樣的設計可能會很常見(指向自身類型的結構體——鏈表)
但是,當自身循環引用發生的時候會怎麼樣呢? 下面就來看看這麼兩句代碼
CountedPtr<A> pA(new A());
這裏我們新建一個資源,並且把這個資源的管理權移交給pA這個引用計數型智能指針對象管理。如此,pA中的引用計數被初始化爲1。
pA->hold(pA);
這裏,我們把pA對象傳入給實例化的A對象中的引用計數型智能指針m_ptr,m_ptr執行這樣的一個成員函數:
//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
if (this != &p) {
dispose();
ptr = p.ptr;
count = p.count;
++*count;
}
return *this;
}
因爲這裏很明顯不是自身賦值,A中的m_ptr和pA不是同一個對象,所以進入if結構中調用下面的內容。dispose是用作清理,因爲m_ptr並沒有指向任何東西,所以第一個函數並沒有真正的意義。然後
m_ptr.ptr = pA.ptr;
m_ptr.count = pA.count;
++(*m_ptr.count); //(*pA.count)也被++
到此,pA的引用計數爲2
嗯,下面就pA這個對象理所當然的離開了作用域,調用其析構函數:
~CountedPtr () throw() {
dispose();
}
噢,是一個轉向,調用其private成員函數dispose():
void dispose() {
if (--*count == 0) {
delete count;
delete ptr;
}
}
很簡單,將引用計數-1,由2變成1,不爲0,所以if結構內的語句不被執行。
由此,我們又製造了一個完美的太空垃圾……
這樣的循環引用問題應該是在設計的過程中就應該避免的,如果用UML語言描述
A中持有一個 引用計數型智能指針 的語義就是 這個 持有關係 是需要在 A消失的時候所持有的對象也隨之消失(這正是智能指針的作用,在脫離作用域自動清除其持有的資源)。如此就構成了組合 關係。如果要表示 聚合 關係,即有 部分-整體 關係但是部分不隨整體的消失而消失,這就不是 智能指針 所表達的語義。
還有可能遇見的循環引用就是 A1 持有 A2, A2 持有 A1 的情況……
這樣A1,A2中對雙方的引用計數都是2,當一方“銷燬”的時候,雙方的應用計數都變爲1,實際上並沒有銷燬任何東西,製造了兩個完美無暇的太空垃圾~
這裏又引發出一個問題,這樣的資源泄漏問題實際上還是由程序員自身引起的。
C++之所以是一個很容易出錯的語言,很大一部分在於其資源的管理權力全權交給了程序員。這樣的權力到底是造福了程序員還是迷惑了程序員呢?
這裏我卻想起了蜘蛛俠中的一句名言: “一個人能力有多大,責任就有多大!”
對C++中指針的指責不是一天兩天了,其易錯性無可厚非,但是它卻給了你其他語言無法給你的能力!這就是我的觀點,你能力有這麼大,你就有責任來治理好這些資源。而非一再推卸責任。如果真的是要推卸責任,也就應該去選擇其他那些剝奪你的能力而減少你的責任的語言,因爲你有選擇權!就像說英語和中文一樣,並沒有哪個人在強迫你,不是麼?熱愛C++是一種態度,對一個語言的利弊都瞭然於心,瞭解其可以做什麼不可以做什麼,怎樣纔可以更好的使用它來做什麼,才能更好的使用它。更何況,there are rarely things that are
not possible in C++。
在沒有看到這篇文章之前,我覺得智能指針能夠應對所有情況,可是看過這篇文章讓我瞭解到智能指針在循環引用的情況下也會出現問題。
另外,我還發現我的operator=函數有問題,具體的問題請參見上面兩篇文章的operator=函數。此外,本實現還有一些問題沒有考慮:線程安全,if(智能指針)等。
天太晚了,所以就不排版了,請見諒!