2015年(67)
分类: LINUX
2015-03-26 10:43:10
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: # # 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 #includeusing 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
输出为:
is null this is print
这个类最有效的使用场景是当出现大量互指指针时,那么指向对象的指针有效性判断就尤其重要,而这个类可以完美解决这个问题。
可能想的比较深的朋友会问,既然引用计数都已经用上了,那么为什么不直接通过引用计数来析构呢?
其实这几天我也在尝试,C++是否能引入完美的引用计数进行对象管理,而最终卡在一个地方,即:
如果,在类的构造函数里面,需要将引用计数对象构造出来,那么引用计数就会出现问题,如:
class A { public: A() { Count t(this); } virtual ~A() {} }; Count c = new A();
这个时候就会出现问题,除非把Count构造的计数对象放到一个对象池中管理,但是又会增加对象查找的成本,所以最终放弃了这个想法。
另外一点就是,C/C++的指针在很多情况下是最方便的,过度的封装很可能会弄巧成拙,所以适度就好。
OK,惯例代码还是放到googlecode上: