通过引用计数解决野指针的问题(C&C++)

来源:互联网 发布:php类库有哪些 编辑:程序博客网 时间:2024/05/18 11:31

C/C++代码中,野指针问题历来已久,当然,大家都知道new/delete要成对出现:

A *p = new A();delete p;p = NULL;

然而现实中却并不是总是如此简单,考虑如下例子:

class A{public:  C() {}  virtual ~C() {}};class B{public:  B() {    m_pA = NULL;  }  virtual ~B() {}  void SetA(A* p)  {    m_pA = p;  }private:  A* m_pA;};A* pA = new A();B* pB = new B();pB->SetA(pA);delete pA;pA = NULL;//此时B中的m_pA已经无效,但是m_pA仍然不等于NULL,所以用 != NULL来判断不会有任何作用

简单来说,即pA被赋值为NULL,对B中的m_pA没有产生影响,那么怎么才能产生影响呢?

我们有两个做法:

第一种,在A的析构函数里面去B.SetA(NULL),但是这个相当于A去操作了B的数据,这是不合理的。而且当外面的指针非常多的时候,也根本不可能实现。

第二种方法呢?是的,我们可以用二级指针。

考虑如下代码:

class A{public:  C() {}  virtual ~C() {}};class B{public:  B() {    m_ppA = NULL;  }  virtual ~B() {}  void SetA(A** pp)  {    m_ppA = pp;  }private:  A** m_ppA;};A** ppA = new (A*)();(*ppA) = new A();B* pB = new B();pB->SetA(ppA);delete (*ppA);(*ppA) = NULL;//这个时候,B中的m_ppA也会收到影响,即*m_ppA == NULL

这样确实可以解决野指针的问题,但是同时也引入了另一个问题,那就是ppA本身该什么时候释放呢?答案是:当最后一个引用ppA的类释放掉的时候。

最后一个,对,我们可以使用引用计数!

OK,正式放出我们的代码,其中使用了引用计数来确定当最后一个类释放掉的时候,ppA指针的内存被析构:

/*=============================================================================## FileName: ptr_proxy.h# Desc: 这个类的作用,就是为了解决互指指针,不知道对方已经析构的问题##   Author: dantezhu#Email: zny2008@gmail.com# HomePage: http://www.vimer.cn##  Created: 2011-06-13 15:24:12#  Version: 0.0.1#  History:#   0.0.1 | dantezhu | 2011-06-13 15:24:12 | initialization#=============================================================================*/#ifndef __PTR_PROXY_H__#define __PTR_PROXY_H__#include #include  #include   #include    #include   #include    #include     #include      #include     #include      #include       
using namespace std; template class ptr_proxy { public: ptr_proxy(const T* pobj=NULL) : m_ppobj(NULL), m_pcount(NULL) { if (pobj == NULL) { return; } init(pobj); } ptr_proxy(const ptr_proxy& rhs) // 拷贝构造函数 { m_ppobj = rhs.m_ppobj; // 指向同一块内存 m_pcount = rhs.m_pcount; // 使用同一个计数值 add_count(); } virtual ~ptr_proxy() { dec_count(); } /** * @brief 如果指向的对象被释放了,一定要调用这个函数让他知道 */ void set2null() { if (m_ppobj) { (*m_ppobj) = NULL; } } /** * @brief copy构造函数 * * @param rhs 被拷贝对象 * * @return 自己的引用 */ ptr_proxy& operator=(const ptr_proxy& rhs) { if( m_ppobj == rhs.m_ppobj ) // 首先判断是否本来就指向同一内存块 return *this; // 是则直接返回 dec_count(); m_ppobj = rhs.m_ppobj; // 指向同一块内存 m_pcount = rhs.m_pcount; // 使用同一个计数值 add_count(); return *this; // 是则直接返回 } ptr_proxy& operator=(const T* pobj) { if(m_ppobj && *m_ppobj == pobj) // 首先判断是否本来就指向同一内存块 return *this; // 是则直接返回 dec_count(); init(pobj); return *this; } /** * @brief 获取内部关联的obj的指针 * * @return */ T* true_ptr() { if (m_ppobj) { return *m_ppobj; } else { return NULL; } } /** * @brief 获取内部关联的obj的指针 * * @return */ T* operator*() { return true_ptr(); } /** * @brief 获取内部关联的obj的个数 * * @return 个数 */ int count() { if (m_pcount != NULL) { return *m_pcount; } return 0; } /** * @brief 判断智能指针是否为空 * * @return */ bool is_null() { if (m_ppobj == NULL || (*m_ppobj) == NULL) { return true; } return false; } protected: void init(const T* pobj) { m_ppobj = new (T*)(); *m_ppobj = (T*)pobj; m_pcount = new int(); // 初始化计数值为 1 *m_pcount = 1; } void add_count() { if (m_pcount == NULL) { return; } (*m_pcount) ++; } /** * @brief 计数减1 */ void dec_count() { if (m_pcount == NULL || m_ppobj == NULL) { return; } (*m_pcount)--; // 计数值减 1 ,因为该指针不再指向原来内存块了 if( *m_pcount <= 0 ) // 已经没有别的指针指向原来内存块了 { //我们不去主动析构对象 //free_sptr(*m_ppobj);//把对象析构 if (m_ppobj != NULL) { delete m_ppobj; m_ppobj = NULL; } if (m_pcount != NULL) { delete m_pcount; m_pcount = NULL; } } } protected: T** m_ppobj; int* m_pcount; }; template class IPtrProxy { public: IPtrProxy() { m_ptr_proxy = (T*)this; } virtual ~IPtrProxy() { m_ptr_proxy.set2null(); } ptr_proxy & get_ptr_proxy() { return m_ptr_proxy; } protected: ptr_proxy m_ptr_proxy; }; #endif

我们来写段测试代码测试一下:

#include  #include    #include      #include        #include          #include            #include              #include                #include                  #include                    #include           
#include \"ptr_proxy.h\" using namespace std; class A : public IPtrProxy { public: A() {} virtual ~A() {} }; class B : public IPtrProxy { public: B() {} virtual ~B() {} void print() { printf(\"this is print\\\\n\"); } void SetAPtr(const ptr_proxy& pptr) { m_ptr_a = pptr; } void check() { if (m_ptr_a.is_null()) { printf(\"is null\\\\n\"); } else { printf(\"is not null\\\\n\"); } } ptr_proxy m_ptr_a; }; int main(int argc, char **argv) { A* a = new A(); B* b = new B(); b->SetAPtr(a->get_ptr_proxy()); delete a; b->check(); b->get_ptr_proxy().true_ptr()->print(); delete b; return 0; }

输出为:

is nullthis is print

这个类最有效的使用场景是当出现大量互指指针时,那么指向对象的指针有效性判断就尤其重要,而这个类可以完美解决这个问题。

可能想的比较深的朋友会问,既然引用计数都已经用上了,那么为什么不直接通过引用计数来析构呢?

其实这几天我也在尝试,C++是否能引入完美的引用计数进行对象管理,而最终卡在一个地方,即:

如果,在类的构造函数里面,需要将引用计数对象构造出来,那么引用计数就会出现问题,如:

class A{public:    A() {        Count t(this);    }    virtual ~A() {}};Count c = new A();

这个时候就会出现问题,除非把Count构造的计数对象放到一个对象池中管理,但是又会增加对象查找的成本,所以最终放弃了这个想法。

另外一点就是,C/C++的指针在很多情况下是最方便的,过度的封装很可能会弄巧成拙,所以适度就好。

OK,惯例代码还是放到googlecode上:

http://code.google.com/p/vimercode/source/browse/#svn%2Ftrunk%2Fptr_proxy

  • 本文来自:Linux学习网
0 0
原创粉丝点击