osg的内存管理主要通过osg::Referenced和osg::ref_ptr两个重要的类以智能指针的形式实现的。
类图
osg::Referenced类图如下图所示:
osg::ref_ptr类图如下图所示:
堆内存释放问题
众所周知,C++内存管理中一个老大难的问题就是堆上的内存释放问题,主要面临两个问题,一是由于程序员的疏忽常常忘记释放,二是意外的提前释放导致其它正在使用此指针的对象出现程序运行崩溃。
智能指针解决方案
智能指针如何解决以上问题?
第一个问题的解决方案,对比栈内存使用方法,我们可以发现栈内存的释放是不需要程序员加以特别关注的,在栈上申请的内存在函数或类的生命周期结束后可以自动释放,依据这个特性,智能指针的解决办法是“将堆上的内存管理转化到栈上”。
简单说,就是将需要管理的堆的指针对象进行封装,被管理的对象的指针地址保存在智能指针内部,同时引用计数器加一,而智能指针本身是一个栈上生成的对象,它可以被操作系统自动处理回收,操作系统在处理回收智能指针的同时,会自动触发智能指针的析构函数,同时引用计数器减一,此时同步释放堆的指针对象内容。
关键代码实现:
//智能指针构造函数
ref_ptr(T* ptr) : _ptr(ptr) { if (_ptr) _ptr->ref(); }
//智能指针析构函数
~ref_ptr() { if (_ptr) _ptr->unref(); _ptr = 0; }
//引用计数+1
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
}
//引用计数-1
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;
}
简化类图如下图所示:
第二个问题的解决方案是智能指针实现了观察者模式来实现在真正释放对象堆内存前通知到正在使用此对象的有关各方。
首先看一下这个函数:
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;
}
}
signalObserversAndDelete函数被调用的时机就是智能指针的析构函数执行,其中的signalObjectDeleted函数其实就是在通知使用此对象的其他对象,当前对象马上就要被删除了,请其他对象先进行针对此对象的处理,例如执行从队列或map中将其移除不再使用等操作,保证当前对象被删除后也不会引起关联使用对象运行奔溃,起到一个通知作用,这是一种非常典型的观察者设计模式。
引用计数器的本质
从osg::Referenced类设计来看,只要其他类是派生于osg::Referenced类,那么引用计数器就会作为此类的一个属性进行跟随,那么究竟什么是引用,其实本质就是对osg::ref_ptr的定义使用。以下代码就能说明此问题:
void UseSmartPointer(osg::Node* nodePtr)
{
osg::ref_ptr<osg::Node> sp1(nodePtr);
osg::ref_ptr<osg::Node> sp2(nodePtr);
}
int main()
{
osg::Node* nodePtr = osgDB::readNodeFile("src/glider.osg");
UseSmartPointer(nodePtr);
osg::ref_ptr<osg::Node> sp3(nodePtr);
return 0;
}
在上述代码中,nodePtr指针被传入UseSmartPointer函数中,经过了两次智能指针的引用,但是智能指针sp1和sp2都是局部变量,所以在UseSmartPointer函数结束时都会触发其析构函数,结果会导致nodePtr指针被释放,虽然sp3语句执行时不会报错,但此时nodePtr指针指向的内存区域已经不正确,如果后续使用的话一定会出问题。具体调试结果如下图所示:
所以,总结起来,在osg中对指针内存的申请一定要配合智能指针一起使用,不要单独使用,在混用模式下很容易出现上述问题。
智能指针使用注意事项
1、如果想要使用智能指针,特别需要注意的是被管理的对象必须要继承osg::Referenced,否则会引起编译错误,代码如下所示:
class Derived {
public:
void func(char *str)
{
std::cout << str << std::endl;
};
};
int main() {
osg::ref_ptr<Derived> derived = new Derived();
derived->func("This is smart ptr test!!"); //compile error
return 0;
}
2、需要深刻理解智能指针get和release两种方法的内在区别,不适当的使用,可能会导致程序运行崩溃。