osg中的超级指针

来源:互联网 发布:懂球帝软件 编辑:程序博客网 时间:2024/05/22 16:03

超级指针

  智能指针对于C++程序员来说并不是一个陌生的东西,它在c++的内存管理中有着很广泛的应用。
  说到内存管理,简单来说就是内存的创建和回收,但是这时候就会出现一个问题,当我们要使用一个东西的时候,我们肯定会想着去创建它,但是当我们使用完之后还能想的起来要去删除它所占用个的内存空间吗?这个大家可能就不能保证了。那怎么办呢?那就只能等到程序结束之后,系统再释放掉这段内存空间了。这会儿肯定有人想说,我这儿不就new了一小段内存空间吗,内存空间那么大,就这么一点也不算啥。额,这个要是都这么想的话,那就完蛋了,积少成多、滴水穿石各个地方浪费的内存空间积攒到一起这也是一个很可观的数字啊。而且有的程序可能跑一会儿还行,但是很多情况下,一个程序可能会运行一天、一个月、甚至更久,这么看来,内存管理还是挺重要的。
  既然说要对内存进行管理,那么如何来管理呢。大家可能经常会听到这样一句话——“用栈中的空间来管理堆中的内存。”这句话是什么意思呢?在解释之前,我们需要先来看看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_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer;
  

  上面这一段代码中,定义了一个viewer变量,这个变量是位于栈上面的,它会在函数结束之后自动释放掉,而我们在后面使用new操作符创建的一个osg::Viewer对象则是分配在堆上面的,是不会自动释放的,除非等到整个程序结束。将他们通过“=”符号连接起来,这样就能用智能指针来管理我们创建的这个osg::Viewer对象了。具体如何管理,我们来看看这两个类的源代码就了然了(怎么来看源码呢?在编译的时候,cmake最终生成了项目,vs打开之后就可以看到源码)。

#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");   returnnode;}

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

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

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

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

  在上面第二中情况中,我们使用get方法来返回一个模型对象是会引起内存泄露的,那我们现在使用release方法来返回是否会有问题呢?在运行之后我们会发现用release方法来返回并不会出现问题,这是为什么呢?
  首先我们来看,当程序运行到第一句的时候“=”运算符相当于是给模型对象增加了一个引用,所以会导致模型对象的引用计数加一变为1
  其次,当我们返回的时候,由于我们调用的是release方法,在前面介绍release函数的时候我们也介绍过其源码,他仅仅是将所管理的对象的引用计数减一,就算减一之后对象的引用计数变为了0也不会去释放掉对象空间。所以在此处调用release之后,尽管模型对象的引用计数已经变为了0,但是却并不会释放模型对象所占用的内存空间。并且node中的用于保存其所管理的指针对象的_ptr也被置为0,即node已经与模型对象无关了,已经不能再管理模型对象了。
  之后,由于函数调用结束,处于栈区的node对象被编译器自动释放掉了,由于其中存放其所管理的指针对象的_ptr变量已经被置为0,它也不能再去调用模型对象的unref方法了,就这样整个creat程序执行完成。

demo

  如需更详细的代码,请访问本人的git-hellomafei。

0 0
原创粉丝点击