OSG中的智能指针

OSG中的智能指针

说到内存管理,大家可能经常会听到这样一句话——“用栈中的空间来管理堆中的内存。”这句话是什么意思呢?在解释之前,我们需要先来看看C++中的内存分区,与我们一般觉得内存就是一整块不同,C++中内存是分成了不同的区域的,一般来说有以下几个区域:

静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。它主要存放静态数据、全局数据和常量。

栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

堆区:亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。

大家可以看到,栈区是会在函数结束之后自动释放掉的,而堆区则不会,所以聪明的程序猿前辈们想出了一个办法——用栈中的内存管理堆中的内存,具体到OSG中,那就是通过模板类ref_ptr(以继承自Reference类子类作为模板参数的模板类)以及Reference类(在OSG中基本上所有涉及到内存管理的类都是继承Reference类的)的配套使用来实现对内存的管理。在Reference类中他维护了一个_refCount(引用计数)变量来记录当前对象被引用的次数,并且提供了ref()与unref()两个方法来实现对引用计数变量_refCount的++和–操作,而在模板类ref_ptr中则主要通过调用这两个方法来实现对引用计数变量的管理,进而实现对内存的管理。在ref_ptr类中维护了一个_ptr变量用于保存其需要进行管理的指针。例如:

osg::ref_ptrosg::Group root = new osg::Group;

上面这一段代码中,定义了一个root变量,这个变量是位于栈上面的,它会在函数结束之后自动释放掉,而我们在后面使用new操作符创建的一个osg::Group对象则是分配在堆上面的,是不会自动释放的,除非等到整个程序结束。将他们通过“=”符号连接起来,这样就能用智能指针来管理我们创建的这个osg::Group对象了。具体如何管理,我们来看看这两个类的源代码就了然了。

#include <osg/Config>  
namespace osg {  
template<typename T> class observer_ptr;  
  
/** Smart pointer for handling referenced counted objects.*/  
template<class T>  
class ref_ptr  
{  
    public:  
        typedef T element_type;  
    //一系列的构造函数,第一个为默认构造函数,  
        ref_ptr() : _ptr(0) {}  
//第二个需要一个类型为“T*”类型的参数,即需要一个继承自Reference类的对象指针作为参数,并且通过一个if语句判断是否赋值成功,如果成功则调用其所管理的对象指针的ref方法使引用计数加一。  
        ref_ptr(T* ptr) : _ptr(ptr) { if (_ptr) _ptr->ref(); }  
//传入一个模板参数相同的ref_ptr对象,如果成功引用,则调用ref是引用计数加一  
        ref_ptr(const ref_ptr& rp) : _ptr(rp._ptr) { if (_ptr) _ptr->ref(); }  
//复制构造函数,将另一个ref_ptr对象作为参数传入当前对象中——对所管理的指针增加一个引用,适用与模板参数不同的情况  
        template<class Other> ref_ptr(const ref_ptr<Other>& rp) : _ptr(rp._ptr) { if (_ptr) _ptr->ref(); }  
    //用于接受弱指针作为参数的构造函数。  
        ref_ptr(observer_ptr<T>& optr) : _ptr(0) { optr.lock(*this); }  
    //析构函数,在此析构函数中,首先调用unref函数使得管理的指针中的引用计数减一,之后将当前指针置空。  
        ~ref_ptr() { if (_ptr) _ptr->unref();  _ptr = 0; }  
    //重载赋值操作符,处理将一个智能指针赋值给另一个智能指针的情况,与直接调用ref函数不同,在这个函数中通过调用assign函数来处理。例如:osg::ref_ptr<osg::Node> node1 = new osg::Node; osg::ref_ptr<osg::Node> node = node1;(ref_ptr都是管理的Node类对象)  
        ref_ptr& operator = (const ref_ptr& rp)  
        {  
            assign(rp);  
            return *this;  
        }  
    //重载赋值操作符,用于处理管理不同类型的智能指针之间的引用。例如:osg::ref_ptr<osg::Geode> node1 = new osg::Node; osg::ref_ptr<osg::Node> node = node1;(相当于将子类对象转化为父类对象)  
        template<class Other> ref_ptr& operator = (const ref_ptr<Other>& rp)  
        {  
            assign(rp);  
            return *this;  
        }  
    //重载赋值操作符,用于处理将一个普通指针交由超级指针管理。例如:osg::ref_ptr<osg::Node> node1 = new osg::Node;这不就和我们上面的例子中用到的一样么?我们new了一个osg::Group,之后将其返回的地址通过这个等于符号传给创建的root对象,在我们使用“=”运算符的时候不就是调用的这个函数么?  
        inline ref_ptr& operator = (T* ptr)  
        {  
            if (_ptr==ptr) return *this;  
            T* tmp_ptr = _ptr;  
            _ptr = ptr;  
            if (_ptr) _ptr->ref();  
            // unref second to prevent any deletion of any object which might  
            // be referenced by the other object. i.e rp is child of the  
            // original _ptr.  
            if (tmp_ptr) tmp_ptr->unref();  
            return *this;  
        }  
    //这一部分是在模板类ref_ptr中用于比较的运算符进行一些重载,使得此类能够获得一些普通指针所具有的功能,如‘==’ 、‘!=’以及大小与等操作符。  
        // comparison operators for ref_ptr.  
        bool operator == (const ref_ptr& rp) const { return (_ptr==rp._ptr); }  
        bool operator == (const T* ptr) const { return (_ptr==ptr); }  
        friend bool operator == (const T* ptr, const ref_ptr& rp) { return (ptr==rp._ptr); }  
        bool operator != (const ref_ptr& rp) const { return (_ptr!=rp._ptr); }  
        bool operator != (const T* ptr) const { return (_ptr!=ptr); }  
        friend bool operator != (const T* ptr, const ref_ptr& rp) { return (ptr!=rp._ptr); }  
        bool operator < (const ref_ptr& rp) const { return (_ptr<rp._ptr); }  
  
    // follows is an implmentation of the "safe bool idiom", details can be found at:  
    //   http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool  
    //   http://lists.boost.org/Archives/boost/2003/09/52856.php  
  
    private:  
        typedef T* ref_ptr::*unspecified_bool_type;  
  
    public:  
        // safe bool conversion  
        operator unspecified_bool_type() const { return valid()? &ref_ptr::_ptr : 0; }  
#endif  
    //重载与普通指针相关的运算符,如‘*’、‘->’。使其具有指针的功能,并设置一个get()方法用于获取智能指针类所管理的指针对象  
        T& operator*() const { return *_ptr; }  
        T* operator->() const { return _ptr; }  
        T* get() const { return _ptr; }  
    //重载运算符‘!’,并设置一个valid()方法用于判断智能指针类所管理的指针对象是有效,有效则返回1,无效则返回0;  
        bool operator!() const   { return _ptr==0; } // not required  
        bool valid() const       { return _ptr!=0; }  
    //release方法,用于在返回一个对象的时,并能防止内存泄露。它与get方法最大的区别就是get方法直接返回所管理的指针,而此处是先调用unref_nodelete()方法之后才返回其所管理的对象,这在后面会具体讲到。  
        T* release() { T* tmp=_ptr; if (_ptr) _ptr->unref_nodelete(); _ptr=0; return tmp; }  
    //交换两个智能指针所管理的对象  
        void swap(ref_ptr& rp) { T* tmp=_ptr; _ptr=rp._ptr; rp._ptr=tmp; }  
  
    private:  
    //用于对指针引用时进行一些处理,对新添加的引用执行ref操作,而对以前的引用执行unref操作  
        template<class Other> void assign(const ref_ptr<Other>& rp)  
        {  
            if (_ptr==rp._ptr) return;  
            T* tmp_ptr = _ptr;  
            _ptr = rp._ptr;  
            if (_ptr) _ptr->ref();  
            // unref second to prevent any deletion of any object which might  
            // be referenced by the other object. i.e rp is child of the  
            // original _ptr.  
            if (tmp_ptr) tmp_ptr->unref();  
        }  
    //将其他模板参数的类设置为当前类的友元。  
        template<class Other> friend class ref_ptr;  
    //私有变量,用于存放所管理的指针  
        T* _ptr;  
};  

上面就是ref_ptr类的源码的主要部分,大家可以看到在这个模板类中,出现最多的两个函数也就是ref()和unref()了吧,这也可以看出这个类的主要功能,它其实就是对Reference类中的引用计数变量进行改变,增加引用则加一,减少引用则减一,如此而已。而这一切的实现,都是通过保存_ptr里面的Reference类对象来实现的。

这里面还有一个问题那就是ref_ptr类提供的两个函数get()与release(),在看王锐老师的Beginner‘s Guide中,里面也详细介绍过在我们需要返回一个对象的时候,使用release()方法能够保证不会出内存泄露的问题,而我也是在查看了源码之后才真正发现其内在的差异。单单是看ref_ptr的源码,我们会发现get()方法直接就返回了_ptr中所存放的对象地址,而release()方法中则是先调用unref_nodelete()函数再返回_ptr对象所存放的地址。这两个方法到底有什么区别呢?我们先看看Reference源码再说。这里面我们只是截去部分相关代码来讨论:

//ref()方法,在这个方法中只做了一件事,就是对_refCount进行++运算;  
inline int Referenced::ref() const  
{  
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)  
    return ++_refCount;  
#else  
    if (_refMutex)  
    {  
        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);  
        return ++_refCount;  
    }  
    else  
    {  
        return ++_refCount;  
    }  
#endif  
}  
//unref()方法,在此方法中首先对_refCount进行--运算,之后再判断经过—运算之后的引用计数是否等于0,若等于0(意味着没有对象引用此段内存空间,可以被释放)则将needDelete赋值为true。在函数最后,对needDelete进行判断,如果需要被释放,则调用signalObserversAndDelete(true,true)方法,在这个函数中将会具体执行对对象的删除。  
inline int Referenced::unref() const  
{  
    int newRef;  
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)  
    newRef = --_refCount;  
    bool needDelete = (newRef == 0);  
#else  
    bool needDelete = false;  
    if (_refMutex)  
    {  
        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);  
        newRef = --_refCount;  
        needDelete = newRef==0;  
    }  
    else  
    {  
        newRef = --_refCount;  
        needDelete = newRef==0;  
    }  
#endif  
  
    if (needDelete)  
    {  
        signalObserversAndDelete(true,true);  
    }  
    return newRef;  
}  
//在这个函数中具体执行对对象的删除,它首先对其所有观察者进行删除(应该是观察者模式之类的,暂时也不是太理解,可以暂时忽略,注意后面才是本节重点)。之后再判断是否存在DeleteHandler,当我们需要自定义删除的时候,我们可以自己写一个DeleteHandler实现我们想要的功能,如果没有,则直接执行delete操作,对对象进行删除  
void Referenced::signalObserversAndDelete(bool signalDelete, bool doDelete) const  
{  
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)  
    ObserverSet* observerSet = static_cast<ObserverSet*>(_observerSet.get());  
#else  
    ObserverSet* observerSet = static_cast<ObserverSet*>(_observerSet);  
#endif  
  
    if (observerSet && signalDelete)  
    {  
        observerSet->signalObjectDeleted(const_cast<Referenced*>(this));  
    }  
  
    if (doDelete)  
    {  
        if (_refCount!=0)  
            OSG_NOTICE<<"Warning Referenced::signalObserversAndDelete(,,) doing delete with _refCount="<<_refCount<<std::endl;  
  
        if (getDeleteHandler()) deleteUsingDeleteHandler();  
        else delete this;  
    }  
}  

自此,看到这个delete心总算是放下了,终于把你给删掉了,大家可以看到,为了达到自动释放的目的,OSG还是做了很多工作的,单单是这个删除就进行了这么多的判断。服了。。。诶,对了,还有一个问题还没解决呢,刚刚说的unref_nodelete()函数去哪儿了呢?别急别急,我们现在就来看看这个函数。

int Referenced::unref_nodelete() const  
{  
#if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)  
    return --_refCount;  
#else  
    if (_refMutex)  
    {  
        OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);  
        return --_refCount;  
    }  
    else  
    {  
        return --_refCount;  
    }  
#endif  
}  

啊哈,是不是感觉被耍了,还以为这函数有多那啥复杂呢,其实就是简单的一个功能,对引用计数执行–运算。但是这个方法和上面的unref()方法有何区别呢?单从字面意思来看,unref大概就是取消引用的意思吧,那unref_nodelete不就是取消引用但是不删除咯。的确诶,这个函数与unref的差别大概也就是没有判断引用计数是否到0,更不用说执行什么释放了。但是有人可能就会问了,这样难道不会造成内存泄露么?前面已经提到过了,release主要是用在函数返回一个对象的时候。举几个例子大家就会明白了。

现在我们想要创建一个函数creat()用于从文件中读取一个模型的,并返回此模型对象,并最后在主函数中将读取的结果添加到root节点中,并运行,主函数代码如下:

int main()
{
		osg::ref_ptr<osg::Group> root = new osg::Group;
		root->addChild(creat());
		osgViewer::Viewer* viewer = new osgViewer::Viewer;
		viewer->setSceneData(root);
		return viewer->run();
}

对于creat函数我们可能会想到这样几种情况:

1、osg::Node* creat()
{
  	  osg::Node*node = osgDB::readNodeFile("cow.osg");
     return  node;
}

这种方法里面,我们并不使用智能指针,直接使用C++中的普通指针实现,毫无疑问,这样肯定是正确的,最后程序能够正常运行。

2、osg::Node* creat()
{
   	osg::ref_ptr<osg::Node>node = osgDB::readNodeFile("cow.osg");
  	 return node.get();
}

在这种情况下,我们使用智能指针来管理从文件中读取的模型节点,并在程序最后通过调用get方法来获取node所管理的模型对象让其返回,这样应该没问题了吧。但实际上并不是这样,大家是否还记得智能指针的特点么——“用栈区空间管理堆中的空间”,node作为在栈区中分配的空间,在这个函数调用结束之后就编译器自动释放掉了,即node对象被释放掉了,那么相当于是管理模型对象的智能智能已经被释放掉了,那么模型对象就会被执行unref方法对引用计数进行减一操作,减一之后模型对象中的引用计数就为0了,那么就会调用delete函数来将其释放掉了。也就是说,当我们在主函数中调这个函数之后,返回的是地址空间是已经被释放掉了的。所以程序在运行时会直接就崩溃了。

3、osg::Node* creat()
{
   osg::ref_ptr<osg::Node>node = osgDB::readNodeFile("cow.osg");
   return node.release();
}

在上面第二中情况中,我们使用get方法来返回一个模型对象是会引起内存泄露的,那我们现在使用release方法来返回是否会有问题呢?在运行之后我们会发现用release方法来返回并不会出现问题,这是为什么呢?

首先我们来看,当程序运行到第一句的时候“=”运算符相当于是给模型对象增加了一个引用,所以会导致模型对象的引用计数加一变为1;

其次,当我们返回的时候,由于我们调用的是release方法,在前面介绍release函数的时候我们也介绍过其源码,他仅仅是将所管理的对象的引用计数减一,就算减一之后对象的引用计数变为了0也不会去释放掉对象空间。所以在此处调用release之后,尽管模型对象的引用计数已经变为了0,但是却并不会释放模型对象所占用的内存空间。并且node中的用于保存其所管理的指针对象的_ptr也被置为0,即node已经与模型对象无关了,已经不能再管理模型对象了。

之后,由于函数调用结束,处于栈区的node对象被编译器自动释放掉了,由于其中存放其所管理的指针对象的_ptr变量已经被置为0,它也不能再去调用模型对象的unref方法了,就这样整个creat程序执行完成。

转载地址:http://blog.csdn.net/u010133496/article/details/40215471

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值