OSG智能指针

OSG智能指针

        ref_ptr<> 利用引用计数管理分配在堆上的内存,计数为(0)时自动释放内存规则1:对所有从Referenced 继承的类,都用ref_ptr<>规则2:绝对不要返回ref_ptr<> 指向的地址,返回ref_ptr<> 它自己规则3:绝对不要用ref()、unref(),(或者release()、unref_nodelete())除非你真的真的真的知道你在干什么规则4:任何Referenced 的派生类(无论直接或间接),析构函数都要设为protected,使对象不会被分配到栈上规则5(规则1的例外):循环引用时,当我们知道此对象在当前范围内会被另外的ref_ptr 引用,就有必要(小心地)使用简单指针

     首先弄清楚:1、分配在堆(heap)上的内存是指调用 new() 或者 malloc() 之类的方法动态分配的那些,需要由程序员自己负责跟踪和回收(delete、free),否则会引起内存泄漏;分配在栈(stack)上的内存由编译器管理,不用我们操心,比如局部变量。2、引用计数(reference count)是用来管理分配在堆上的对象的。若有个外部实体需要用到这个对象,引用计数+1,用完则-1,当减为0时对象就释放它自己。

      下面看 ref_ptr<>:  ref_ptr<>是模版类,可以实例化为指向任何从Referenced继承的对象指针。严格地说,ref_ptr<>自身是分配在栈上的,但是保存的是分配在堆上的内存的地址。它的作用就是实现自动引用计数:ref_ptr<>所指对象的引用计数在复制构造函数或者"="操作符时 +1,在析构函数里 -1。  现在比较一下:void SomeClass::someMethod(){ osg::ref_ptr nodeRefPtr = new osg::Node; osg::Node *nodePtr = new osg::Node;}  上面的函数返回时,nodeRefPtr 调用其析构函数,对象的引用计数减为(0),对象自动释放它自己;而nodePtr 在函数外无效,它原来指向的内存仍然留在堆里,而且无法跟踪。  再看这个例子:void SomeClass::someMethod(osg::Group *group){ osg::ref_ptr nodeRefPtr = new osg::Node; group->addChild( nodeRefPtr.get() );}  函数中group的ref_ptr<>使对象的引用计数增为(2),当函数返回时,nodeRefPtr 析构使对象的计数减为(1),所以当group还在使用此对象时它不会释放自己。对于.get(),暂时可以理解为返回osg::Node对象的地址。  一般用 bool valid() 来判断智能指针是否指向有效的地址。

     需要注意的几点:

      1、混用 * 和 ref_ptr<>  

       看下面这段代码,doSomethingWithNode 函数利用ref_ptr<>将对象的引用计数增为(1),函数返回时又减为(0),此时对象释放自己占用的内存。当doSomeStuff()再对那块内存操作时就会出错。void SomeClass::doSomethingWithNode( osg::Node *node ){ osg::ref_ptr nPtr = node; nPtr->doit();}void SomeClass::doSomeStuff(){ osg::Node *node = new osg::Node; doSomethingWithNode( node ); node->doitAgain(); // node 现在指向已经删除的数据,引起access violation}规则1:对所有从Referenced 继承的类,都用ref_ptr<>

       2、用ref_ptr<> 作函数返回值  

         下面的代码中,createANode()动态分配的内存在它退出时就已经释放,initializeOrSomething()无法在释放之前增加引用计数。void SomeClass::initializeOrSomething(){ osg::ref_ptr nodeRefPtr = createANode();}osg::Node *SomeClass::createANode(){ // 按照规则1 osg::ref_ptr nRefPtr = new osg::Node; return nRefPtr.get(); // 指向的内存会在函数退出时释放}  有两个方法解决:  1)createANode()中不用ref_ptr<>,可能造成内存泄漏;  2)手动增加计数nRefPtr->ref(),还需要自己调用unref()  所以最好还是将ref_ptr<>作为返回值:osg::ref_ptr SomeClass::createANode(){ osg::ref_ptr nRefPtr = new osg::Node; return nRefPtr; // OK!}规则2:绝对不要返回ref_ptr<> 指向的地址,返回ref_ptr<> 它自己规则3:绝对不要用ref()、unref(),(或者release()、unref_nodelete())除非你真的真的真的知道你在干什么

    3、不适当地应用从Referenced 继承的对象  

      osg 和Producer 的基类把它们的析构函数定义为protected,强制用户将这些类分配在堆上而不是栈上。但是,下面定义的MyNode 却是合法的,它的析构函数设为public。这样程序员可以合法(但是不适当)地将这个类作为局部变量放在栈上,这时如果有个ref_ptr<> 引用它,就会使引用计数减为(0),导致栈变量试图删除它自己。class MyNode : public osg::Node{public: MyNode() {} virtual ~MyNode() {} // public destructor}void someClass::doSomething(){ MyNode myNode; DoSomethingWithMyNode( &myNode );}void someClass::doSomethingWithMyNode( MyNode *n){ osg::ref_ptr mnRefPtr = n;}规则4:任何Referenced 的派生类(无论直接或间接),析构函数都要设为protected,使对象不会被分配到栈上

     4、循环引用  

      当两个从Referenced 继承的类实例互相引用对方时,就可能造成循环引用。class B;class A : public osg::Referenced{public: A() {} void setB(B *b);private: osg::ref_ptr _b;};class B : public osg::Referenced{public: B(A *a) : _a(a) {}private: osg::ref_ptr _a;};void A::setB( B *b) { _b=b; }int main(){ osg::ref_ptr a = new A; //(a's count is 1) osg::ref_ptr b = new B(a.get()); //(b's count is 1 and a's count is 2) a->setB(b.get()); //(b's count is 2) return 0;}// a 和 b 在这里失效,引用计数各-1,变为(1),对象没有删除解决方法是利用简单指针:class B;class A : public osg::Referenced{public: A(): _b(0L) {} void setB(B *b);private: // Not a ref pointer B *_b;};class B : public osg::Referenced{public: B(A *a) : _a(a) {}private: // Not a ref pointer A *_a;};void A::setB( B *b) { _b=b; }int main(){ osg::ref_ptr a = new A; // &a's count is 1 osg::ref_ptr b = new B(a.get()); // &b's count is 1 and &a's count remains 1 a->setB(b.get()); // &b's count remains 1 return 0;}// a and b go out of scope, counts go to 0规则5(规则1的例外):循环引用时,当我们知道此对象在当前范围内会被另外的ref_ptr 引用,就有必要(小心地)使用简单指针

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值