04-VTK可视化管线(1)


4、VTK可视化管线

通过第3章的学习,我们已经了解了VTK的一些基础概念。在这一章里,我们将更深入地学习VTK,其中包括VTK的系统框架结构、引用计数、智能指针、Observer/Command设计机制以及本章的重点内容——VTK可视化管线结构。通过本章的学习,可能你对VTK的设计框架将会有更深一层的理解。

所谓追根溯源,首先我们先了解一下VTK里绝大多数类的共同的父类vtkObjectBase和vtkObject。

4.1 vtkObjectBase和vtkObject

vtkObjectBase是一个抽象基类,派生出绝大多数的VTK类。它是VTK里所有引用计数(Reference Counting)类的基类,著名的子类包括:vtkCommand,vtkInformationKey和vtkObject。

4.1.1 引用计数

如果很多对象有相同的值,将这个值存储多次是很无聊的。更好的办法是让所有的对象共享这个值的实现。这么做不但节省内存,而且可以使得程序运行更快,因为不需要构造和析构这个值的拷贝。引用计数就是这样一个技巧,它允许多个有相同值的对象共享这个值的实现。引用计数是个简单的垃圾回收体系,只要其它对象引用某对象(记为对象O),对象O就会存在一个引用计数,当最后引用对象O的对象移除,O对象就会自动析构。VTK里使用引用计数的好处是,可以实现数据之间的共享而不用拷贝,从而达到节省内存的目的。

我们可以看一个简单的例子(4.1.1_ReferenceCounting):

 

///ReferenceCount.cpp/

  1:  #include"vtkSmartPointer.h"

  2:  #include"vtkBMPReader.h"

  3:  #include"vtkImageData.h"

  4:  

  5:  int main(int argc, char*argv[])

  6:  {

  7:  vtkSmartPointer<vtkBMPReader>reader = vtkSmartPointer<vtkBMPReader>::New();

  8:   reader->SetFileName("../test.bmp");

  9:    reader->Update();

 10:  

 11:   std::cout<<"Reference Count of reader->GetOutput (BeforeAssignment) = "

 12:       <<reader->GetOutput()->GetReferenceCount()<<std::endl;

 13:  

 14:    vtkSmartPointer<vtkImageData> image1 = reader->GetOutput();

 15:   std::cout<<"Reference Count of reader->GetOutput (Assignto image1) = "

 16:       <<reader->GetOutput()->GetReferenceCount()<<std::endl;

 17:   std::cout<<"Reference Count of image1 = "

 18:       <<image1->GetReferenceCount()<<std::endl;

 19:  

 20:   vtkSmartPointer<vtkImageData> image2 = reader->GetOutput();

 21:   std::cout<<"Reference Count of reader->GetOutput (Assignto image2) = "

 22:       <<reader->GetOutput()->GetReferenceCount()<<std::endl;

 23:   std::cout<<"Reference Count of image2 = "

 24:       <<image2->GetReferenceCount()<<std::endl;

 25:  

 26:    return 0;

 27:  }

//

 

程序输出结果如图4.1所示。


图4.1ReferenceCount运行结果

在ReferenceCount示例里,我们先用vtkBMPReader读入一幅BMP图像test.bmp,在赋值之前我们输出了reader->GetOutput()的引用计数值,其值为1(使用方法New()创建对象以后,初始的引用计数值就等于1);然后我们创建了一个vtkImageData类型的对象image1,并把reader的输出赋给了image1,这时image1就指向了reader的输出,也就是说,reader的输出多了一个引用,这个时候输出的reader->GetOutput()和image1的引用计数都为2;接着我们又创建一个类型同样为vtkImageData的对象image2,同样也是把reader的输出赋值给image2,这时,image2也指向reader的输出,亦即reader的输出又多了一个引用,所以输出的reader->GetOutput()和image2的引用计数值变成了3。image1和image2的数据结构可以简单地描述为图4.2。


图4.2image1,image2,reader->GetOutput()及引用计数之间的结构关系

一旦某个对象的引用计数等于0时,就表明没有别的对象再引用它,它的使命也宣告完成,程序就会自动的析构这个对象。在ReferenceCount这个示例里,我们看不到引用计数减少的相关代码,这是因为我们使用了智能指针vtkSmartPointer。

4.1.2 智能指针

智能指针会自动管理引用计数的增加与减少,如果检测到某对象的引用计数值减少为0,则会自动地释放该对象的资源,从而达到自动管理内存的目的。

在前面的内容我们已经介绍过,VTK里,要创建一个对象可以用两种方法,一种是使用vtkObjectBase里的静态成员变量New(),用Delete()方法析构;另一种就是我们示例里使用多次的使用智能指针vtkSmartPointer<T>。

对于第一种方法,用New()创建的对象,程序最后必须要调用Delete()方法释放对应的内存,而且由于vtkObjectBase及其子类的构造函数都是声明为受保护的,这意味着它们不能在栈区(栈区上的内存是由编译器自动分配与释放的,堆区上的内存则是由程序员分配和手动释放的。)上分配内存。比如:

vtkBMPReader*reader = vtkBMPReader::New(); //创建vtkBMPReader对象

……

reader->Delete();//程序最后要调用Delete(),这里并没有直接析构对象,而是使引用计数值减1。

用New()创建的对象,如果没有用Delete()方法删除的话,程序有可能会出现内存泄漏,即用户负责对象内存的管理。

如果使用智能指针创建的对象,则无需手动调用Delete()方法让引用计数减少,因为引用计数的增加与减少都是由智能指针自动完成的。使用智能指针时,首先是要包含智能指针的头文件:#include "vtkSmartPointer.h"。vtkSmartPointer是一个模板类,所需的模板参数就是待创建的对象的类名,如:

vtkSmartPointer<vtkImageData>image = vtkSmartPointer< vtkImageData >::New();

注意上面一行代码等号右边写法,不能写为:

vtkSmartPointer<vtkImageData > image = vtkImageData::New();

也就是不能把对象的原始指针赋给智能指针,上行代码编译的时候可以通过,但程序退出时会有内存泄漏,就是因为智能指针无法自动释放该对象的内存,如图4.3所示。


图4.3 将对象的原始指针赋予智能指针会引起内存泄漏

如果没有给对象分配内存,仍然可以使用智能指针,比如

  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值