20.超级指针(难点)
关于超级指针的问题由来已久,用的颇广。在 OSG 中问题是这样提出来的,有这样一个场景,有一辆卡车, 卡车上有一个箱子。如果说卡车在运行过程中,箱子突然掉了,这样就知道该把箱子的结点删除。那么如 果说还有一个箱子,在地上放着,从感觉上来讲他们应该用同一块内存。卡车上的箱子虽然掉了,但是地 上的箱子还在,所以箱子所占的资源不能被粗鲁的释放掉。
这样就引用了超级指针的机制,其实就是引用一个计数器,这个计数器会计算这个箱子被引用的次数,被 别人引用一次这个计数器增加一,别人不用一次,即:释放一次,则计数器减一。当减至 0 时,内存放掉 不用。
21.超级指针详解 (难点)
内容来自 OpenSceneGraph基本渲染理论
Leandro Motta Barros
Start: August 17th 02005
译:FreeSouth
让我们来以一个定义开讲:一个 资源 必须在它被使用之前被申请,而在它使用之后不久被释放,也许我们有很多的应用程
序在使用同一片资源,一个不需要了而另一个却还需要使用这片资源。这两种情况都涉及到页面(在打开前必须处于关闭状态)
和数据的移动(在移动前可能被加锁或做一些包裹处理)。在 OpenGL 当中,也同样有一些关于申请与释放资源的例子(一个示例是纹理函数的名称被命名为 glGenTextures(), 它就必须被函数 glDeleteTextures()释放)。
在事物扩展的事例当中,事物拥有者使用 AllocateTing()来分配该事物, 然后拥有者便有责任来管理他们的分配与释放操作。
在 OSG 当中,有一些稍微有些复杂的细节来实现这些操作,也就是一些事物可能有多个拥有者。比如,在场景图 1.7 所示当中,
类 osg::geode 中含有 box 绑定到两个父结点身上,到底哪个负责他们的分配与释放操作呢。
在这种情况之下,只要还有一个引用指向该资源,该资源就不应该被释放。所以,大多数的 OSG 对象都有一个内部的计数
器用来计算有多少个指针指向它。当没有指针指向该资源(同样,可以称做对象)时,它的计数器会变为 0,当计算器为 0 时就
可以放掉对象。
幸运的是,我们程序员不需要手工的来维护这些引用计数:这就是超级指针为什么存在的原因。在 OSG 当中超级指针被一
个称为 osg::ref_ptr<>的类模版来实现。无论在什么时候,只要该对象被引用,则在 osg::ref_ptr<>当中的计数器会自动增加, 通过这种方式实现了资源的自动管理,当不需要使用它时,它会在不久后释放,在申请它时会自动分配空间。
22.超级指针代码解释
下面这个例子会示例如何使用 OSG 的超级指针,例子中有一些代码如下所示:
SmartPointers.cpp
1 #include
2 #include
3 #include
4 #include
5
6 void MayThrow()
7 {
8 if (rand() % 2)
9 throw "Aaaargh!";
10 }
11
12 int main()
13 {
14 try
15 {
16 srand(time(0));
17 osg::ref_ptr
18
19 // This is OK, albeit a little verbose.
20 osg::ref_ptr
21 MayThrow();
22 group->addChild (aGeode.get());
23
24 // This is quite safe, too.
25 group->addChild (new osg::Geode());
26
27 // This is dangerous! Don’t do this!
28 osg::Geode* anotherGeode = new osg::Geode();
29 MayThrow();
30 group->addChild (anotherGeode);
31
32 // Say goodbye
33 std::cout << "Oh, fortunate one. No exceptions, no leaks.\n";
34 }
35 catch (...)
36 {
37 std::cerr << "’anotherGeode’ possibly leaked!\n";
38 }
39 }
23.代码解释:
srand(time(0));
srand(time(0)) 就是给这个算法一个启动种子,也就是算法的随机种子数,有这个数以后才可以产生随机数,用1970.1.1至今的秒数,初始化随机数种子。
Srand是种下随机种子数,你每回种下的种子不一样,用Rand得到的随机数就不一样。为了每回种下一个不一样的种子,所以就选用Time(0),Time(0)是得到当前时时间值(因为每时每刻时间是不一样的了)。
20 osg::ref_ptr
21 MayThrow();
22 group->addChild (aGeode.get());
第 20 行到 22 行,示例了一个比较安全的方式来使用超级指针,一个 osg::ref_prt<>(叫做 aGeode)在第 20 行被初始
化为 osg::Geode(资源)。在这一点上,关于 geode 的引用计数器在堆内会被置 1(因为 osg::ref_ptr<>中只分配了一个Geode,故可以命令为 aGeode)。
在稍后的第 22 行,geode 被当做一个孩子加入到一个组当中。这个操作一旦执行结束,组对 geode 的引用计数就会增加 1,变为 2。
现在,有哪些比较烂的事情发生了呢?如果我们在第 21 行调用 MayThrow()之后,会发生干什么事情呢?好的,aGeode将会超出范围,然后被析构掉。然而,在它的计数减至 0 之后,它仍会合适的处理该 geode,这时也没有内存泄漏。
28 osg::Geode* anotherGeode = new osg::Geode();
29 MayThrow();
30 group->addChild (anotherGeode);
坏的、错误的、危险的以及应该被责备的方式来管理内存是从第 28 到第 30 行的做法。它看起来和第一种是一样的,但是 geode 是被 new 分配的且存储在一个普通的指针当中。如果 MayThrow()在第 29 行被抛出,没有人会调用 Delete,这时 geode 就发生了泄漏。
24.移动/旋转/缩放模型
移动/旋转/缩放其实都是对矩阵进行操作,在 OSG 当中,矩阵可以当作一个特殊的结点加入到 root 当中, 而矩阵下也可以另入结点,而加入的结点就会被这个矩阵处理过,比如移动过/旋转过/缩放过。在 OSG 中 控制矩阵的类可以为 osg::MatrixTransform。下面看一个示例,示例对 osgCool.osg 进行了各种操作。
功能:移动/旋转/缩放模型,这里加入了四个 glider,一个是默认加入在最中间,一个向上移 2 单位,一 个是向下移 2 单位且缩放 0.5 倍,另一个是向右 4 单位,缩放 0.5 且平躺 45 度。
25.代码及解释如下:这部分代码就用到了超级指针
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char **argv)
{
osgViewer::Viewer viewer;
osg::ref_ptr < osg::Group> root = new osg::Group() ;
osg::ref_ptr < osg::Node> glider = osgDB::readNodeFile("glider.osg") ;
osg::ref_ptr < osg::MatrixTransform> trans = new osg::MatrixTransform ;
trans ->setMatrix(osg::Matrix::translate(0, 0, 2)) ;
trans ->addChild(glider.get()) ;
osg::ref_ptr < osg::MatrixTransform> scale = new osg::MatrixTransform ;
scale ->setMatrix(osg::Matrix::scale(0.5, 0.5, 0.5)*osg::Matrix::translate(0, 0, -2)) ;
scale ->addChild(glider.get()) ;
osg::ref_ptr < osg::MatrixTransform > rot = new osg::MatrixTransform ;
rot ->setMatrix(osg::Matrix::rotate(osg::DegreesToRadians(45.0), 1, 0, 0)*osg::Matrix::scale(0.5, 0.5, 0.5)*osg::Matrix::translate(4, 0, -2)) ;
rot ->addChild(glider.get()) ;
root ->addChild(glider.get()) ;
root ->addChild(trans.get()) ;
root ->addChild(scale.get()) ;
root ->addChild(rot.get()) ;
//添加状态事件
viewer.addEventHandler( new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()) );
//窗口大小变化事件
viewer.addEventHandler(new osgViewer::WindowSizeHandler);
//添加一些常用状态设置
viewer.addEventHandler(new osgViewer::StatsHandler);
viewer.setSceneData(root.get());
viewer.realize();
return viewer.run();
}
osg::ref_ptr < osg::Node> glider = osgDB::readNodeFile("glider.osg") ;
读入一个 glider.osg 模型
trans ->setMatrix(osg::Matrix::translate(0, 0, 2)) ;
申请一个矩阵使加入矩阵的东西沿Z 轴平移 2 个单位,在 OSG 中坐标轴是可以设置的,默认 X 轴是 X 轴,向里的是 Y 轴,而向上是 Z 轴,这与传统的 OPENGL 坐标轴多有不同。
scale ->setMatrix(osg::Matrix::scale(0.5, 0.5, 0.5)*osg::Matrix::translate(0, 0, -2)) ;
申请一个矩阵使加入该矩阵的模型向下移动两个单位,值得一提的是,移动和缩放以及旋转都 是对矩阵进行操作,这些操作如果要叠加直接矩阵相乘就可以了,可以查阅一下矩阵的基本知识,最 好看图形学的相关书籍中关于矩阵操作一节。
rot ->setMatrix(osg::Matrix::rotate(osg::DegreesToRadians(45.0), 1, 0, 0)*osg::Matrix::scale(0.5, 0.5, 0.5)*osg::Matrix::translate(4, 0, -2)) ;
申请一个矩阵使加入该矩阵的模型向下移动两个单位,向右移动四个单位,且绕 X 轴转 45 度。 这里的几个矩阵也是叠加的,函数 DegreesToRadians 可以把角度转为弧度。
root ->addChild(glider.get()) ;
root ->addChild(trans.get()) ;
root ->addChild(scale.get()) ;
root ->addChild(rot.get()) ;
把这些矩阵都加入到 root 当中。