珍爱生命,远离野指针

Background

估计只要是C++程序员,没有一个不痛恨这个野指针啦,而对于我们这种只能通过log来debug的程序员来说,其恨更深。

Solution

每次看到形如下面的代码时

A* p1 = new A;

A* p2 = p1;

delete p1;

我都有一种想要将p2也置成空的冲动,但往往都不遂我心愿,因为在实际中p1,p2的出现实在是神出鬼没,让你防不胜防也烦不胜烦。

鲁迅先生说过: 不在沉默中暴发就在沉默中灭亡。幸好,我没有灭亡,所以我要暴发。

在防够了,烦饱了以后,我下定决心,要端掉这个让我受尽折磨地暗堡。

复杂问题的解决方案往往都是简单而“暴力”地,因为问题的难解决一般来说都是因为”暴力”无处着力。我坚信这个规则,所以我的思路很简单,就是要在delete p1的时候把p2也置为空。

计算机科学中有个神话般的格言:计算机科学中的大部分问题都可以通过增加一个中间层来解决。我希望这一次神话能得以延续。

好了,想想吧,我们要解决的问题实际上只有一个,那就是要找一个机制,让p2能知悉它所指向对象的状态(在这里是生命周期)。如果我们把A的生命周期作为一个类提出来,把它叫LifeObject吧,并给每个A的实例一个配备一个LifeObject对象,再让p1和p2指向这个生命周期对象,这样我们只须在A的构造函数中创建一个LifeObject对象,在析构函数中将告诉LifeObject,这样在使用p1和p2的时候就知道当前使用的指针是否是有效的啦。

其关系示意如下:

                                     

Implement

思路有了,就像有了作战计划,那当然要开始行军了:

那个示意图告诉了我们致少4件事:

  1. p1,p2现在不能是指向A的裸指针啦,因为一个裸指针无法自己做到从LifeObject中获取相应的信息。需要封装一下,就叫SafePtr吧;
  2. SafePtr要提供销毁A的方法,取名为Destroy;
  3. LifeObject是有引用计数的,因为它要负责在A销毁之后,通知p1,p2,…pn,同时它还必须是创建在heap上的;
  4. LifeObject中要有A的状态信息,在A构造函数中新建LifeObject时将其状态信息置为valid,在A析构时置为invalid。

最后,其结构图大致如下:

                        

从这个结构图上,我们可以看到:

  1. LifeObject是用SafeObject的一个指针m_pPointee来指示SafeObject的状态的,在SafeObject创建时把自己的地址赋给m_pPointee,在析构时将m_pPointee设为NULL;
  2. 通过将LifeObject的析构函数设为私有,并提供销毁方法Destroy,可以保证它只能在heap上创建,同时除了它的friend SafeObject以外没有人能创建它。
  3. SafePtr提供了判空函数IsNUll和NotNull;

在具体实现的时候,还要考虑的问题是SafePtr要像裸指针一样,能够从SafePtr<Derived>转化为SafePtr<Base>以及从SafePtr<Base> 安全转换到SafePtr<Derived>一些事。大家可以参见其源码

/* * SmartPointer.h * * Created on: 2010-9-3 * Author: li_shugan@126.com */ #ifndef SAFEPTR_H_ #define SAFEPTR_H_ #include "SafeObject.h" template<typename ReferenceType> class SafePtr { public: explicit SafePtr(ReferenceType* pPointer); //SafePtr(SafePtr& other); template<typename NewType> SafePtr(SafePtr<NewType>& other); //const SafePtr& operator = (SafePtr& other); template<typename NewType> const SafePtr& operator = (SafePtr<NewType>& other); ~SafePtr(); public: ReferenceType* operator->()const; ReferenceType& operator*()const; void Destroy(void); bool IsNull(void); bool NotNull(void); template<typename NewType> operator SafePtr<NewType>() { return SafePtr<NewType>(r_pValue); } template<typename NewType> SafePtr<NewType> Cast() { return SafePtr<NewType>(dynamic_cast<NewType*>(r_pValue)); } private: void Init(void); private: ReferenceType* r_pValue; LifeObject* r_pRefObj; template<class NewType> friend class SafePtr; }; #include "SafePtr.inl" #endif /* SMARTPOINTER_H_ */

/* * SmartPointer.inl * * Created on: 2010-9-3 * Author: li_shugan@126.com */ #include <cstddef> template<typename ReferenceType> SafePtr<ReferenceType>::SafePtr(ReferenceType* pPointer): r_pValue(pPointer), r_pRefObj(NULL) { if(NULL != pPointer) { r_pRefObj = pPointer->GetCountObj(); r_pRefObj->AddRef(); } } template<typename ReferenceType> SafePtr<ReferenceType>::~SafePtr() { if(NULL != r_pRefObj) { r_pRefObj->Release(); } } //template<typename ReferenceType> //SafePtr<ReferenceType>::SafePtr(SafePtr<ReferenceType>& other): // r_pValue(other.r_pValue), // r_pRefObj(other.r_pRefObj) //{ // Init(); //} template<typename ReferenceType> template<typename NewType> SafePtr<ReferenceType>::SafePtr(SafePtr<NewType>& other): r_pValue(other.r_pValue), r_pRefObj(other.r_pRefObj) { Init(); } template<typename ReferenceType> template<typename NewType> const SafePtr<ReferenceType>& SafePtr<ReferenceType>::operator = (SafePtr<NewType>& other) { if(r_pRefObj != other.r_pRefObj) { LifeObject* oldPtr = r_pRefObj; r_pValue = other.pPointer; r_pRefObj = other.r_pRefObj; Init(); if(NULL != oldPtr) { oldPtr->Release(); } } return *this; } template<typename ReferenceType> void SafePtr<ReferenceType>::Init(void) { if(NotNull()) { r_pRefObj->AddRef(); } else { r_pValue = NULL; r_pRefObj = NULL; } } template<typename ReferenceType> ReferenceType* SafePtr<ReferenceType>::operator->()const { return r_pValue; } template<typename ReferenceType> ReferenceType& SafePtr<ReferenceType>::operator*()const { return *r_pValue; } template<typename ReferenceType> void SafePtr<ReferenceType>::Destroy(void) { if(NULL != r_pRefObj) { r_pRefObj->Destroy(); r_pRefObj->Release(); r_pValue = NULL; r_pRefObj = NULL; } } template<typename ReferenceType> bool SafePtr<ReferenceType>::IsNull(void) { return (NULL == r_pRefObj) || !r_pRefObj->IsValid(); } template<typename ReferenceType> bool SafePtr<ReferenceType>::NotNull(void) { return !IsNull(); }

/* * SafeObject.h * * Created on: 2010-9-4 * Author: li_shugan@126.com */ #ifndef SAFEOBJECT_H_ #define SAFEOBJECT_H_ class SafeObject; class LifeObject { public: explicit LifeObject(SafeObject *pPointee); void Destroy(void); int AddRef(void); int Release(void); bool IsValid(void){return 0 != r_pPointee;} private: //You should always create the instance of LifeObject in heap. ~LifeObject(){}; //can not been copy or assignment. LifeObject(const LifeObject& other); LifeObject& operator = (const LifeObject& other); private: int m_nRefCount; SafeObject* r_pPointee; friend class SafeObject; }; class SafeObject { public: SafeObject(); virtual ~SafeObject() = 0; LifeObject* GetCountObj(void)const{return m_pLifeObject;} int test(){return 2;} private: LifeObject* m_pLifeObject; }; #endif /* SAFEOBJECT_H_ */

/* * SafeObject.cpp * * Created on: 2010-9-4 * Author: li_shugan@126.com */ #include <cstddef> #include "SafeObject.h" LifeObject::LifeObject(SafeObject *pPointee): m_nRefCount(0), r_pPointee(pPointee) { } void LifeObject::Destroy(void) { delete r_pPointee; } int LifeObject::AddRef(void) { return ++m_nRefCount; } int LifeObject::Release(void) { if(0 == (--m_nRefCount)) { delete this; } return m_nRefCount; } SafeObject::SafeObject() { // TODO Auto-generated constructor stub m_pLifeObject = new LifeObject(this); } SafeObject::~SafeObject() { m_pLifeObject->r_pPointee = NULL; }

How to use it?

  1. 首先你要让你的类继承处SafeObject,如下:

class Test: public SafeObject

  1. 像使用智能指针一样使用它

SafePtr<Test> pp(new Test);

SafePtr<Test> pp1 = pp;

if(pp1.NotNull())

{

pp1->output();

pp1->Fun();

SafePtr<SafeObject> pp3(pp);

cout<<pp3->test()<<endl;

}

pp.Destroy();

if(pp1.NotNull())

{

pp1->Fun();

}

Test Code如下:

 
  
/* * main.cpp * * Created on: 2010-9-4 * Author: Administrator */ #include "SafePtr.h" #include "SafeObject.h" #include <iostream> using std::cout; using std::endl; class Base1 { public: void output() { cout<<__FUNCTION__<<endl; } }; class Test:public Base1,public SafeObject { public: ~Test() { cout<<__FUNCTION__<<endl; } public: void Fun(void) { cout<<"You invoke fun"<<endl; } }; #include <memory> int main(int argc,char** argv) { SafePtr<Test> pp(new Test); SafePtr<Test> pp1 = pp; if(pp1.NotNull()) { pp1->output(); pp1->Fun(); SafePtr<SafeObject> pp3(pp); cout<<pp3->test()<<endl; } pp.Destroy(); if(pp1.NotNull()) { pp1->Fun(); } SafePtr<Test> pp4 = pp; if(pp4.NotNull()) { pp4->Fun(); } SafePtr<Test> pp5 = pp1; if(pp5.NotNull()) { pp4->Fun(); } return 0; }

Advantage

我想大家已经看出来了,接口的定义和智能指针很相似,只是多了一个Destroy函数而已,所以它的优点就有两个啦:

  1. 野指针在这儿灰飞烟灭了;
  2. 如果不调用destroy函数,它就是一智能指针,用它可以防止内存泄漏。

Disadvantage

虽然它解决了C++中两大问题,但是它的缺点也是有目共睹的,除了常规智能指针的缺陷外它还多出了两个:

  1. 不能调用delete来销毁一个指针,你要通通换成对Destroy函数的的调用;
  2. 如果你想要享受特权公民的待遇,你就得让你的类从SafeObject继承一下。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值