http://www.cppblog.com/OnTheWay2008/archive/2010/12/17/136810.html
很久以前就对基于引用计数的指针指针很感兴趣,今天突然又一次想到这个问题,所以就写了一个基于引用技术的智能指针。该智能指针的具体实现方式如下:

2

3

4

5

6

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131


2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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(智能指针)等。
天太晚了,所以就不排版了,请见谅!