Open Scene Graph 内存管理-ref_ptr

在Osg中有一个类被使用的最多,那就是ref_ptr,OSG中提供了一种自动管理内存的机制,这种机制涉及到两个类:第一个就是ref_ptr,另外一个就是osg::Referenced.

  osg::Reference支持通过引用计数的方式来控制自己的生命周期,它期望,每次被一个新客户引用的时候,新客户能给他添加引用计数;而当客户不需要再使用它的时候,希望客户能够减少引用计数.如果引用计数达到0,那么osg::Reference就会析构自己,释放内存.但是这个过程对客户要求比较高,谁能记得住每次都添加引用和减少引用呢? ref_ptr能!这也是它为什么存在的原因.ref_ptr重载了大量的操作符来实现这个添加引用和减少引用的过程.

下面是一个使用ref_ptr的基本原则:

  1.如果希望用ref_ptr来管理内存,你的类需要继承osg::Referenced

  2.如果你的类的对象想要持有一个osg::Referenced的派生类的指针,你应该持有一个ref_ptr然后,让ref_ptr来持有一个指针

  3.如果你只是在一个函数中短时间的使用一个osg::Referenced的派生类的指针,你不需要使用ref_ptr,直接用就可以了.如果你在短时间使用后,会传递给其他的对象来持有这个指针,你也不需要用ref_ptr,因为通过ref_ptr来长时间持有这个对象是其他的对象的责任了.

  4.如果有两个类都从osg::Referenced派生,而你想继承这两个类,那就有麻烦了。Osg中的派生都没有用virtual修饰符,所以你的类对象中会出现两份osg::Referenced的东西.ref_ptr实际上不知道该怎么处理了.如果是一个严格的编译器,编译器甚至会报错!.这个时候,你必须做出选择,从其中一个派生,而另一个则采用组合的方式来处理.多继承就是这一点不好.不过话说回来,在代码中应该尽量不使用多继承,除非是从完全没有成员的纯抽象类继承(纯抽象类当做接口用).

下面来一段ref_ptr的代码看看:

1 inline ref_ptr& operator = (T* ptr)
2 {
3 if (_ptr==ptr) return *this;
4 T* tmp_ptr = _ptr;
5 _ptr = ptr;
6 if (_ptr)
7 _ptr->ref();
8 // unref second to prevent any deletion of any object which might
9 // be referenced by the other object. i.e rp is child of the
10 // original _ptr.
11 if (tmp_ptr)
12 tmp_ptr->unref();
13 return *this;
14 }

在给ref_ptr赋值的时候,它首先把新对象给ref一下,然后把原来的对象给unref一下.这个持续比较重要,如果反过来写就不行了.你想想看,如果我把同一个指针给它两次,如果反过来写,对象就会被销毁了! 然后是Referenced的代码:

1 inline void Referenced::ref() const
2 {
3 #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
4 ++_refCount;
5 #else
6 if (_refMutex)
7 {
8 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
9 ++_refCount;
10 }
11 else
12 {
13 ++_refCount;
14 }
15 #endif
16 }
17
18 inline void Referenced::unref() const
19 {
20 #if defined(_OSG_REFERENCED_USE_ATOMIC_OPERATIONS)
21 bool needDelete = (--_refCount == 0);
22 #else
23 bool needDelete = false;
24 if (_refMutex)
25 {
26 OpenThreads::ScopedLock<OpenThreads::Mutex> lock(*_refMutex);
27 --_refCount;
28 needDelete = _refCount<=0;
29 }
30 else
31 {
32 --_refCount;
33 needDelete = _refCount<=0;
34 }
35 #endif
36 if (needDelete)
37 {
38 if (getDeleteHandler())
39 deleteUsingDeleteHandler();
40 else
41 delete this;
42 }
43 }

去掉多线程控制的代码,其原理是很好理解的。

1. 从只能指针中获取原始指针的方法有:

  osg::ref_ptr<osg::Geode> geodePtr = new osg::Geode();

  osg::Geode* obj1 = *geodePtr;       //获取Geode指针

  osg::Geode* obj2 = geodePtr.get(); //获取Geode指针

2.下面代码存在性能问题

  while(1)
  {
    osg::ref_ptr<osg::Geode> geodePtr = new osg::Geode ;
  }
  智能指针只存在while循环中因此每次循坏都会反复将新分配的对象地址传入智能指针,然后ref_ptr对象析构时引用计数归0,
    Geode对象被析构,最终并不会产生内存泄露,但会引起效率问题。
3. 每一个OSG场景对象都维护一个或数个ref_ptr类型的成员变量,因此下面两种操作Geode对象都可以:
  //假设已经存在geodeParent父对象
  osg::ref_ptr<osg::Geode> geodePtr = new osg::Geode;

  geodeParent->setGeode(geodePtr);
  //或者
  osg::Geode* geodePtr = new osg::Geode;
  geodeParent->setGeode(geodePtr);

  另一个需要特别注意的是ref_ptr作为函数返回值的传递时:
  osg::Geode*  createGeode()
  {
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    //下面做法为错误的
    //return geode.get();
    //正确做法
    return geode.release();
  }

  用get()时,当函数体结束时,ref_ptr对象析构同时使geode对象引用计数归零,使得geode对象被析构
  导致返回的指针地址无效。正确做法使用release(),即将引用对象实例输出,同时引用计数归0,但不释放。
4. 综上, 使用ref_ptr的几条规则:
  1. OSG中新创建的场景对象建议使用ref_ptr进行内存的管理;
  2. 对于不使用ref_ptr的对象,引用计数值无意义,无法自动从场景对象中卸载;
      3. 新建对象最为函数结果返回时,建议使用release()传递,并将返回值引入ref_ptr自动管理。

posted @ 2011-10-10 16:39  神の日记  阅读(1919)  评论(0编辑  收藏  举报