摘自:Don Burns《Using Reference Pointers
in
Producer and OpenSceneGraph》
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 引用,就有必要(小心地)使用简单指针
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 引用,就有必要(小心地)使用简单指针