OSG中的智能指针

在OpenSceneGraph中,智能指针(Smart pointer)的概念指的是一种类的模板,它针对某一特定类型的对象(即Referenced类及其派生类)构建,提供了自己的管理模式,以避免因为用户使用new运算符创建对象实例之后,没有及时用delete运算符释放对象,而造成部分内存空间被浪费的后果,也就是所谓的内存泄露错误。

由于OSG中与场景图形有关的大多数类均派生自Referenced类,因此OSG大量使用了智能指针来实现场景图形节点的管理。智能指针的使用为用户提供了一种自动内存释放的机制,即,场景图形中的每一个节点均关联一个内存计数器,当计数器的计数减到零时,该对象将被自动释放。而用户如果希望释放整个场景图形的节点的话,则只需要删除根节点,根节点以下的所有分支节点均会因此被自动删除,不用担心内存泄露的问题。

要使用OSG的智能指针,需要满足以下两个条件:

1、用户的类必须派生自Referenced类,这样才能使用与其自身关联的内存计数器;

2、使用智能指针模板osg::ref_ptr<class T>来定义类的实例,当用户使用该模板定义实例时,内存计数器即被启用并加一;同理,当ref_ptr模板超出其生命范围时,类实例的内存计数器将被减一,如果减到零则对象自动被释放。

此外,要使用智能指针,程序中应当引用以下的头文件:

#include <osg/ref_ptr>

一个使用智能指针的例子如下:

void exampleFunc(){
  osg::ref_ptr<osg::Group> root = new osg::Group;
  osg::ref_ptr<osg::Geode> node1 = new osg::Geode;
  osg::ref_ptr<osg::Geometry> geo1 = new osg::Geometry;
  printf("%d, %d, %d\n", root->referenceCount(), node1->referenceCount(), geo1->referenceCount());
  root->addChild(node1.get());
  node1->addDrawable(geo1.get());
  printf("%d, %d, %d\n", root->referenceCount(), node1->referenceCount(), geo1->referenceCount());
}

这个例子本身并没有什么意义,但是可以通过它了解智能指针的运作流程。

在解读这个例子之前,首先了解一下与ref_ptr和Referenced类相关的主要成员和运算符:

Referenced类

void ref()

这个公共函数使得Referenced类实例的内存计数器值加一。

void unref()

这个公共函数使得Referenced类实例的内存计数器值减一,如果计数器值为零,那么它自动尝试将类的实例删除,释放相应的内存。

int referenceCount()

返回当前内存计数器的数值。

ref_ptr
ref_ptr()

构造函数,不过它什么也不做。用例为:

osg::ref_ptr<osg::Node> node1;
ref_ptr(T* ptr)

构造函数,并为其对象分配新的内存空间,同时对象的内存计数器值加一。用例为:

osg::ref_ptr<osg::Node> node1 = new osg::Node;
ref_ptr(const ref_ptr& rp)

构造函数,其对象将指向一个已有的智能指针对象,同时对象的内存计数器值加一。用例为:

osg::ref_ptr<osg::Node> node2 = node1;
~ref_ptr()

析构函数,执行时对象的内存计数器值减一。

ref_ptr& operator = (const ref_ptr& rp)

重载的赋值运算符,用例为:

node2 = node1;

将node2指向node1,同时将node2(也就是node1)的内存计数器值加一。

ref_ptr& operator = (T* ptr)

重载的赋值运算符,用例为:

node2 = new osg::Node;

将node2指向一个类的实例,同时将node2的内存计数器值加一。

T& operator*() const

返回类实例的值。例如:

osg::ref_ptr<osg::Node> node1 = new osg::Node;

则*node1表示osg::Node

T* operator->() const

返回类的实例。例如:

osg::ref_ptr<osg::Node> node1 = new osg::Node;

则node1->…表示(osg::Node*)->…

T* get() const

返回类的实例。例如:

osg::ref_ptr<osg::Node> node1 = new osg::Node;

则node1.get()表示osg::Node*

bool valid()

返回指针是否有效的标志。

void swap(ref_ptr& rp)

将目前指针所指向的内容与用户输入的数据进行交换。用例为:

node2.swap(node1); //交换两个指针的位置

再看刚才的例子程序,它主要完成了这样的功能:

1、 新建两个节点root和node1,以及一个几何图形geo1;

2、 调用智能指针的get方法,将node1作为root的子节点加入(addChild);

3、 调用智能指针的get方法,将geo1作为node1的绘图数据加入(addDrawable)。

此外,程序还调用referenceCount方法,观察内存计数器的数值。

在主函数中调用此子函数,编译并运行,观察显示的结果,应为:

1, 1, 1

1, 2, 2

可见,当节点和几何图形第一次被创建时,它们的内存计数器自动加一;而将node1作为子节点加入以及将geo1作为图形元件加入的操作,则分别使得这两者的内存计数器再次加一。

使用new运算符使得内存计数器加一,是因为在ref_ptr构造函数中执行了ref()方法。此方法自动为当前实例的内存计数器加一。

函数addChild和addDrawable会使得内存计数器加一,是因为程序中将node1或者geo1加入到一个ref_ptr的向量表中。参照源代码可知,用于保存子节点的向量表为NodeList,其定义为:

typedef std::vector< ref_ptr<Node> > NodeList;

而用于保存Geometry几何数据的向量表为DrawableList,其定义为:

typedef std::vector< ref_ptr<Drawable> > DrawableList;

在执行函数addChild和addDrawable时,使用了向量表模板的push_back方法,将带有智能指针的数据压入向量表中,这一步将使得内存计数器自动加一。具体的执行过程可以参见VC目录下的vector头文件,其中有类同以下的语句段:

……

_Ty _Tmp = _Val;

……

对于NodeList,上文中的_Ty即表示ref_ptr<Node>,而_Val则是压入向量表的数据,因此有:

ref_ptr<Node> _Tmp = node1.get();

参考ref_ptr中第二种构造函数的形式可知,此时系统将调用ref()函数,使得内存计数器再次加一,显然,这一操作对node1也会产生影响。

同理,当执行向量表的pop_back或erase函数时,因为调用了ref_ptr的析构函数,也会使得内存计数器自动减一。执行函数removeChild和removeDrawable即可实现这样的效果。

再看一种常见的情况,代码如下:

for (int i = 0; i < 100; i++){
osg::Node* node = new osg::Node;
……
}

一般情况下,在循环中使用new运算符开辟新的内存空间,如果没有及时释放的话,将产生内存泄露的问题。对于上述的程序段,在运行时如果打开任务管理器,则可以看到程序所占的内存值不断上涨,如果不加以制止的话,甚至可能造成计算机崩溃。

现在将该程序段中使用new运算符的语句行改写如下:

//osg::Node* node = new osg::Node;
osg::ref_ptr<osg::Node> node = new osg::Node;

再次运行该程序,可以发现内存增长的现象消失了,智能指针在这里发挥了不可忽视的作用。分析这一段程序的流程,可见:

1、 进入循环后,首先为node分配一块新的内存区域,同时内存计数器自动加一;

2、 执行其余的代码,如果不对node使用addChild等操作,那么计数器的值始终为1;

3、 到达for循环的结束位置,此时临时变量的生命周期已经结束,则执行~ref_ptr(),在其中自动执行unref()对计数器的值减一,则计数器的值为0,系统将自动释放内存区域。

4、新的循环开始,此时原有的内存区域已被释放,没有出现内存泄露的情况。

综上所述,使用智能指针ref_ptr来包装用户的节点类,几何体类等数据,可以有效地进行内存管理,很大程度上避免了内存泄露现象的发生。而在智能指针的使用过程中,还应当注意以下几点:

1、智能指针模板的应用对象必须派生自Referenced类,否则模板将无法使用。例如:

osg::ref_ptr<osg::Vec3> v;

这样的声明是无法编译通过的,因为Vec3类并不是派生自Referenced类,因此也不具有ref()和unref()这样的成员函数,无法与计数器相关联。

2、不可以直接使用delete运算符删除应用智能指针的对象。事实上这样的语句也无法编译通过,阅读Referenced类的源代码可以发现,Referenced类的析构函数~Referenced()为保护函数(protected类型),直接使用delete运算符调用它是不允许的。

3、不要随意使用ref()和unref()函数来改变内存计数器的值。由于这两个函数都是公共函数,因此这样的操作不会在编译中报错,但是如果内存计数器的数值在程序运行时减为零,以致其对象被释放,那么下面所有针对此对象的操作均可能导致程序崩溃。而这样的变故在正常使用的情况下是决不会出现的,因此,除非用户有特殊需要,否则尽量不要直接使用ref()和unref()来改变内存计数器的值。

4、在OSG中,不使用智能指针而是用形如osg::Node * node的声明方式也是可以的。但是在大型程序中,应当尽量统一使用智能指针来进行内存的管理。此外,有的时候没有统一使用ref_ptr的话,程序也可能出现问题。比如这个例子:

osg::Group* exampleFunc(){
  osg::ref_ptr<osg::Group> root = new osg::Group;
  osg::ref_ptr<osg::Geode> node1 = new osg::Geode;
  root->addChild(node1.get());
  ……

  return root->get();
}

int main(int argc, char** argv){
  ……
  osg::Node* a = exampleFunc()->getChild(0);
  ……
}

由于ref_ptr的生命周期在函数的末尾即告结束,导致函数返回时返回的Group指针其实已经被释放掉了,这样程序编译和链接都不会与错误,但运行时会出现错误,而且这种错误往往难以检测到。为了解决问题,将程序统一修改为ref_ptr的命名方式如下:

osg::ref_ptr<osg::Group> exampleFunc(){
  osg::ref_ptr<osg::Group> root = new osg::Group;
  osg::ref_ptr<osg::Geode> node1 = new osg::Geode;
  root->addChild(node1.get());
  ……
  return root;
}

int main(int argc, char** argv){
  ……
  ref_ptr<osg::Node> a = exampleFunc()->getChild(0);
  ……
}

就可以运行通过了。函数返回时,ref_ptr将再次把内存计数器的数值加一,保证返回的数据有效,且主函数中仍然可以交由智能指针进行内存管理。

时间: 2024-09-27 04:27:38

OSG中的智能指针的相关文章

C++中的智能指针

众所周知.C++中对堆内存的申请与释放全然由用户来控制,这就造成用户在使用的时候常常造成内存泄漏.野指针.反复释放等常见的挂掉问题,所以我们有必要提供一套机制.使得用户仅仅需申请对应的内存,不用管释放的问题,事实上这属于著名的RAII(Resource Acquisition Is Initialization)技术 .在C++中这样的技术称作"智能指针",C++中的智能指针技术越来越受到广泛应用.以下简要介绍下智能指针. 从以上描写叙述中能够看出,我们须要提供一套内存显式申请与隐式释

Android系统篇之----Android中的智能指针

一.前言 今天我们开启Android系统篇的文章了,其实一直想弄,只是之前一直没有太多深入的了解,最近又把这块拿出来好好看了一下,所以想从新梳理一下,来看看Android中的这块知识,首先我们今天来看一下:Android中的智能指针的概念,为什么说先看一下智能指针这个知识呢?因为我们在看Android源码的时候,会发现几乎好多地方都用到了这个东东,所以我们在介绍后面的知识点,先来看看这个吧. 二.问题 那么Android中的智能指针是个什么东西呢?我们知道Android用的Java语言开发的,J

Boost中的智能指针(转)

这篇文章主要介绍 boost中的智能指针的使用.(转自:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html) 内存管理是一个比较繁琐的问题,C++中有两个实现方案: 垃圾回收机制和智能指针.垃圾回收机制因为性能等原因不被C++的大佬们推崇, 而智能指针被认为是解决C++内存问题的最优方案. 1. 定义 一个智能指针就是一个C++的对象, 这对象的行为像一个指针,但是它却可以在其不需要的时候自动删除.注意这个“其不需要的

COCOS2D-X中的智能指针

Cocos2d-x中所有内存管理方式的基础是引用计数,动态分配一个Ref对象后其引用计数为1,并通过retain和release来增持和减少其引用计数.引用计数本身并不能帮助我们进行内存管理. 为了正确地释放对象的内存,Cocos2d-x使用Objective-C里面的自动回收池的机制来管理对象内存的释放.Autorelease有点类似于一个共享的"智能指针",该"智能指针"的作用域为一帧,该帧结束后,它将释放自己的引用计数,此时,如果该对象没有被其他"共

ATL和vc++中的智能指针(分别是CComPtr和_com_ptr_t)

一.智能指针的概念 智能指针是一个类,不是指针,智能指针在所包含的指针不再被使用时候会自动释放该所包含指针所占用的系统资源,而不用手动释放. 原理:智能指针封装了包含指针的AddRef()函数和Release()函数,且在该类不被需要的时候在析构函数里调用包含指针的Release()函数释放包含指针的资源.因此实质是利用类的析构达到调用包含指针的Release()函数的目的. 二.VC++中的智能指针:_com_ptr_t _com_ptr_t实质是一个类模板.使用它时需要提供三个参数:接口的名

实战c++中的智能指针unique_ptr系列-- 使用std::unique_ptr代替new operator(错误:‘unique_ptr’ is not a member of ‘std’)

写了很多篇关于vector的博客,其实vector很便捷,也很简单.但是很多易错的问题都是vector中的元素为智能指针所引起的.所以决定开始写一写关于智能指针的故事,尤其是unique_ptr指针的故事. 这是个开始,就让我们使用std::unique_ptr代替new operator吧! 还是用程序说话: #include<iostream> int main() { while (true) int *x = new int; } 看下任务管理器中的内存: 此时使用智能指针unique

Boost库中的智能指针 shared_ptr智能指针

shared_ptr智能指针的意思即:boost::shared_ptr是可以智能的管理动态分配的内存资源,几个智能指针可以同时共享一个动态分配的内存的所有权. 下面我们通过一个例子来学习一下它的用法: 注 :使用shared_ptr智能指针,要加入#include <boost/shared_ptr.hpp>头文件 class example { public: ~example() { std::cout <<"It's over\n"; } void do

C++中的智能指针(auto_ptr)

实际上auto_ptr 只是C++标准库提供的一个类模板,它与传统的new/delete控制内存相比有一定优势,使用它不必每次都手动调用delete去释放内存.当然有利也有弊,也不是完全完美的. 本文从下面的8个方面来总结auto_ptr使用的大部分内容. 1. auto_ptr是什么? auto_ptr 是C++标准库提供的类模板,auto_ptr对象通过初始化指向由new创建的动态内存,它是这块内存的拥有者,一块内存不能同时被分给两个这样拥有者(auto_ptr).当auto_ptr对象生命

实战c++中的智能指针unique_ptr系列-- unique_ptr的operator=、operator bool、reset、swap、get等介绍

既然打算把unique_ptr写一个系列,就要详尽一点,有些内容也许在vector的时候有个涉及,但是现在还是再谈论一番. 我们要把unique_ptr看做一个类,废话了,它当然是一个类.所以这个类肯定也重载了赋值运算符,即operator=.现在就开始看看operator=在unique_ptr中的使用: 官方描述如下: move assignment (1) unique_ptr& operator= (unique_ptr&& x) noexcept; assign null