VTK 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。 

1 vtkObjectBase::vtkObjectBase
2 {
3       this->ReferenceCount = 1;
4 }

在该类中,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,则调用析构函数来删除对象。

一个简单的实例如下:

1 vtkCamera* camera = vtkCamera::New();      //引用计数为1
2 renderer->SetActiveCamera(camera);         //引用计数为2
3 renderer->Delete();                        //引用计数是1
4 camera->Delete();                          //camera被删除首先调用vtkCamera::New()

函数实例化一个vtkCamera对象camera,此时camera的引用计数初始化为1.然后将camera通过SetActiveCamera()函数传递至一个vtkRenderer对象renderer中。
vtkRenderer::SetActiveCamera()函数的代码如下:

 1 void vtkRenderer::SetActiveCamera(vtkCamera*  cam)
 2 {
 3       if(this->ActiveCamera == cam)
 4         {
 5             return;
 6         }
 7       if(this->ActiveCamera)
 8         {
 9            this->ActiveCamera->UnRegister(this);
10            this->ActiveCamera = NULL;
11         }
12       if( cam )
13         {
14            cam->Register( this );
15         }
16       this-<ActiveCamera == cam;
17       this->Modified();
18       this->InvokeEvent(vtkCommand::ActiveCameraEvent, cam);
19 }

解释:
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,用于存储智能指针中实际生成的对象。智能指针定义为:

1 vtkSmartPoint<vtkCamera> camera = 
2     vtkSmartPointer<vtkCamera>::New();   //引用计数为1

VTKSmartPointer中定义了静态函数New()来生成一个智能指针对象。
该函数的核心在于:会根据模板参数类型来生成一个对象,并将其保存在基类VTKSmartPointerBase的成员变量Object中。

  • VTKSmartPointer中重载了“->”操作符

返回实际模板参数类型的对象,因此可以方便地访问对象的成员函数,如camera->setFocusPosition(0,0,0)。

  • VTKSmartPointer重载了“=”操作符

可以在VTKSmartPointer对象之间进行赋值。在赋值过程中,VTKSmartPointer会自动控制其内部对象指针Object的引用计数加1.

例如:

1 vktSmartPointer<vtkCamera> camera1 =
2     vtkSmartPointer<vtkCamera>::New();
3 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等的基类。
原文地址:https://www.cnblogs.com/ybqjymy/p/14244566.html