关于引用计数型智能指针的一些想法

分类:  C++2008-11-18 15:23  1833人阅读  评论(2)  收藏  举报

 

指针 是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 �0�8- 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++

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值