VTK修炼之道80:VTK开发基础_智能指针与引用计数

1.引用计数

VTK经过多年的开发与维护,已经形成了一套稳定的框架和开发规则。因此,了解这些规则和框架是定制VTK类的基础,这其中用到了大量面向对象的设计模式,例如对象工程模式、观察者/命令模式;还有就是当下非常流行的引用计数与智能指针等高级内存管理等。
内存管理在大型的工程中是非常重要的内容,如果不能有效地管理内存,将严重影响到应用程序的执行效率,甚至可能造成程序崩溃。之前学习C++基础时,教材中都会反复的强调,使用new操作符申请的空间,一定要使用delete来释放。C++中并没有提供高级的内存管理与垃圾回收机制,通常进行手动管理。这对于简单的程序而言可以轻松完成,但是对于大型程序就会疲于应付。最有代表性例子就是,当一个内存块(可以看做一个指针)被多个对象引用时,删除任意一个对象,都可能影响其他对象,引用计数和智能指针刚好用来解决这个问题。

1.1 引用计数

简单来说,引用计数就是每个对象中维护一个引用计数的变量,表示当前对象被多少对象引用。

当一个对象被另一个对象引用时,该对象的引用计数就会加1;当一个对象取消对该对象的引用时,该对象的引用计数减1.当引用计数为0时,程序就会撤销该对象。

VTK中实现引用计数的类为vtkObjectBase。VTK是一个C++类库,在VTK中,C++的继承与多态得到了完美的体现。经过十几年的发展,所有的VTK类集合可以看做一个树状结构,vtkObjectBase则是他们共同的祖先。

这也说明了绝大部分VTK类都支持引用计数。vtkObjectBase中定义了一个ReferenceCount变量,改变量记录了引用计数。当一个vtkObjectBase及其子类对象创建时,ReferenceCount就会被初始化为1。

vtkObjectBase::vtkObjectBase
{
      this->ReferenceCount = 1;
}
在该类中,vtkObjectBase的构造函数、析构函数、拷贝构造函数以及“=”操作符都被声明为“protected”类型,因此不能显示地构造和销毁vtkObjectBase及其子类对象。vtkObjectBase定义了一个静态函数New(),用于生成vtkObjectBase对象。New()函数中调用了构造函数来生成一个对象,并在构造函数中初始化引用计数为1.

1.2 Register()函数以及Unregister()函数实现计数

生成一个vtkObjectBase及其子类对象后,该对象并不会孤立地存在,多数情况下又可能被其他对象引用。这是需要调用Regester()函数实现引用计数加1;Register()函数中有一个vtkObjectBase*类型的形参,代表引用当前对象的其他对象的类型(可以设置为NULL)。因此,引用计数关心的是被引用的数量,而不关心引用者是谁。而Unregister()函数实现引用计数减1,并检查引用计数的数量。当引用计数为零时,自动销毁该对象。

1.3 Delete()函数删除对象释放内存

对于New()的对象,一定要通过Delete()对象来删除。Delete()函数并非直接删除对象,而式调用Unregister()对象将引用计数减1,如果引用计数为0,则调用析构函数来删除对象。

一个简单的实例如下:

vtkCamera* camera = vtkCamera::New();      //引用计数为1
renderer->SetActiveCamera(camera);         //引用计数为2
renderer->Delete();                        //引用计数是1
camera->Delete();                          //camera被删除首先调用vtkCamera::New()
函数实例化一个vtkCamera对象camera,此时camera的引用计数初始化为1.然后将camera通过SetActiveCamera()函数传递至一个vtkRenderer对象renderer中。
vtkRenderer::SetActiveCamera()函数的代码如下:
void vtkRenderer::SetActiveCamera(vtkCamera*  cam)
{
      if(this->ActiveCamera == cam)
        {
            return;
        }
      if(this->ActiveCamera)
        {
           this->ActiveCamera->UnRegister(this);
           this->ActiveCamera = NULL;
        }
      if( cam )
        {
           cam->Register( this );
        }
      this-<ActiveCamera == cam;
      this->Modified();
      this->InvokeEvent(vtkCommand::ActiveCameraEvent, cam);
}
解释:
vtkRenderer中定义了一个vtkCamera对象ActiveCamera。SetActiveCamera()函数用于设置该对象的值。在调用SetActiveCamera()函数时,如果当前已经设置了ActiveCamera,则先UnRegister()该对象,并将其指向NULL。然后,调用Register()函数增加一个引用,说明camera在renderer中被应用,并将ActiveCamera指向camera。此时,camera的引用计数数目为2(如果这里又有一个新的vtkCamera对象通过SetActiveCamera设置,同样先将此前设置的camera对象引用计数减1,再赋值)。而当执行renderer->Delete()函数时,由于renderer的引用计数为1,所以renderer会被销毁,而此时camera又变为了1.当执行camera->Delete()后,其引用计数减一,此时camera计数为零,删除camera对象。

1.4 引用计数的先天缺陷

其实,引用计数并不是十分的完美。本身就有先天的缺陷——对循环引用无能为力。即无法处理对象之间相互引用形成一个环路的情况,例如,VTK中vtkAlgrthm和vtkExecute对象之间。







2.智能指针

智能指针可以完全避免内存泄漏问题。

2.1 智能指针

VTK中智能指针类为vtkSmartPointer。VTKSmartPointer是一个模板类,继承自VTKSmartPointerBase类。VTKSmartPointerBase中定义了一个vtkObjectBase类型的指针对象Object,用于存储智能指针中实际生成的对象。智能指针定义为:

vtkSmartPoint<vtkCamera> camera = 
    vtkSmartPointer<vtkCamera>::New();   //引用计数为1
VTKSmartPointer中定义了静态函数New()来生成一个智能指针对象。
该函数的核心在于:会根据模板参数类型来生成一个对象,并将其保存在基类VTKSmartPointerBase的成员变量Object中。

  • VTKSmartPointer中重载了“->”操作符
返回实际模板参数类型的对象,因此可以方便地访问对象的成员函数,如camera->setFocusPosition(0,0,0)。
  • VTKSmartPointer重载了“=”操作符
可以在VTKSmartPointer对象之间进行赋值。在赋值过程中,VTKSmartPointer会自动控制其内部对象指针Object的引用计数加1.

例如:

vktSmartPointer<vtkCamera> camera1 =
    vtkSmartPointer<vtkCamera>::New();
vtkSmartPointer<vtkCamera> camera2 = camera1;
需要注意的是,此时camera1和camera2的引用计数都等于2。
过程为:首先camera1的vtkCamera对象Object调用Register()函数,自动将引用计数加1,谈后将camera2的Object指向camera1的Object对象。

2.2 智能指针释放内存

当一个智能指针对象的生命周期结束时,会自动调用其析构函数释放内存。在析构函数中会调用其内部对象Object的UnRegister()函数修改引用计数。如果此时的引用计数为0,Object对象会自动释放内存。

3. vtkObjectBase中的几个重要函数

3.1 GetClassName()

该函数用于返回当前类的名字。其通过调用类内保护类型的虚函数GetClassNameInternal()来实现。vtkObjectBase时VTK中绝大对数类的基类,因此这些类都可以访问GetClassName()函数来获取类名。我们必须在这子类中覆盖GetClassNameInternal()函数,这样才会有“多态性”效应。

3.2 IsTypeOf/IsA()

IsTypeOf()是一个静态函数,其参数为一个char类型字符串,通常为一个类的名字,用于判断一个类名是否为vtkObjectBase。

虚函数IsA()则调用IsTypeOf()来判断一个对象的类型。vtkObjectBase的类中都覆盖了IsA(),以便判断实际的类型。

3.3 Print()/PrintSelf()/PrintHeader()/PrintTrailer()

Print()用于输出类的成员变量和状态,其内部调用PrintSelf()/PrintHeader()/PrintTrailer()三个虚函数。

4. 常用vtkObjectBase子类及其继承关系


  • vtkCommand主要涉及观察者/命令模式的实现。
  • vtkInformationKey与vtkInformation搭配使用,用于实现VTK的执行管线。
  • vtkObject是一个非常非常重要的基类!!其子类包括vtkAlgrithm和vtkExecutive两个实现Filter类最重要的类;而vtkDataObject是VTK中所有数据结构类如:vtkPolyData、vtkImageData等的基类。

5.参看资料

1.《C++ primer》
2.《The VTK User’s Guide – 11thEdition》
3.  张晓东, 罗火灵. VTK图形图像开发进阶[M]. 机械工业出版社, 2015.

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MongoDB修炼之道是指在使用MongoDB时遵循的最佳实践和技巧。下面是几个MongoDB修炼之道的要点: 1. 数据建模:设计良好的数据模型是MongoDB应用的基础。要根据应用的需求和查询模式来设计集合和文档结构,以提高查询性能和数据访问效率。 2. 索引优化:合理创建索引可以加快查询的速度。根据查询频率和查询字段的选择性来选择合适的索引,同时要注意索引的大小和性能影响。 3. 查询优化:使用适当的查询操作符和查询策略来优化查询性能。尽量避免全表扫描和大量的数据排序操作,可以使用投影、分页和限制查询结果数量来提高查询效率。 4. 内存管理:合理配置MongoDB的内存资源,根据数据集的大小和访问模式来设置合适的缓存大小和预取策略,以提高数据的读取速度。 5. 分片和复制:对于大规模的数据集和高并发的访问需求,可以考虑使用分片和复制来提高系统的可扩展性和高可用性。 6. 安全设置:为了保护数据的安全性,要启用MongoDB的身份验证机制,设置密码和访问控制,限制只有授权用户才能访问数据库。 7. 定期备份:定期备份数据库以防止数据丢失,可以使用MongoDB提供的备份工具或第三方工具进行数据备份和恢复。 8. 监控和性能调优:监控数据库的性能指标和资源使用情况,及时发现并解决潜在的性能问题,可以使用MongoDB提供的监控工具或第三方工具进行性能调优。 9. 版本管理:及时升级MongoDB的版本,以获取最新的功能和性能改进,并修复已知的安全漏洞和bug问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值