《VTK图形图像开发进阶》第2章——VTK智能指针

发布时间 2023-08-07 16:39:22作者: sdyan404

2.1 引用计数

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

示例2.5_ReferenceCounting

#include <vtkAutoInit.h>
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkInteractionStyle);

#include <vtkSmartPointer.h>
#include <vtkBMPReader.h>
#include <vtkImageData.h>
#include <vtkObject.h>

// MyFunction函数:演示智能指针可以作为函数返回值
vtkSmartPointer<vtkImageData> MyFunction()
{
	vtkSmartPointer<vtkImageData> myObject = vtkSmartPointer<vtkImageData>::New();
	std::cout<<"MyFunction::myObject reference count = "<<myObject->GetReferenceCount()<<std::endl;
	return myObject;
}

//测试文件:data/VTK-logo.bmp
int main(int argc, char* argv[])
{
	/*if (argc < 2)
	{
		std::cout<<argv[0]<<" "<<"BMP-File(*.bmp)"<<std::endl;
		return EXIT_FAILURE;
	}*/
	//演示引用计数:
	vtkSmartPointer<vtkBMPReader> reader = vtkSmartPointer<vtkBMPReader>::New();
	reader->SetFileName("data/VTK-logo.bmp");
	reader->Update();

	std::cout<<"Reference Count of reader->GetOutput (Before Assignment) = "
		<<reader->GetOutput()->GetReferenceCount()<<std::endl;

	vtkSmartPointer<vtkImageData> image1 = reader->GetOutput();
	std::cout<<"Reference Count of reader->GetOutput (Assign to image1) = "
		<<reader->GetOutput()->GetReferenceCount()<<std::endl;
	std::cout<<"Reference Count of image1 = "
		<<image1->GetReferenceCount()<<std::endl;

	vtkSmartPointer<vtkImageData> image2 = reader->GetOutput();
	std::cout<<"Reference Count of reader->GetOutput (Assign to image2) = "
		<<reader->GetOutput()->GetReferenceCount()<<std::endl;
	std::cout<<"Reference Count of image2 = "
		<<image2->GetReferenceCount()<<std::endl;
	//////////////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////////
	//演示智能指针可以作为函数返回值
	//由于函数MyFunction()的返回值是通过拷贝的方式,
	//将数据赋予调用的变量,因此该数据的引用计数保持不变
	std::cout<<"myObject reference count = "
		<<MyFunction()->GetReferenceCount()<<std::endl;

	vtkSmartPointer<vtkImageData> MyImageData = MyFunction();
	std::cout<<"MyFunction return value reference count = "
		<<MyFunction()->GetReferenceCount()<<std::endl;

	std::cout<<"MyImageData reference count = "
		<<MyImageData->GetReferenceCount()<<std::endl;
	//////////////////////////////////////////////////////////////////////////

	//////////////////////////////////////////////////////////////////////////
	//如果没有给对象分配内存,仍然可以使用智能指针:
	vtkSmartPointer<vtkBMPReader> Reader = vtkSmartPointer<vtkBMPReader>::New();
	vtkImageData* pd = Reader->GetOutput();
	//////////////////////////////////////////////////////////////////////////

	system("pause");
	return EXIT_SUCCESS;
}

image

image

2.2 智能指针

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

VTK创建对象有两种方法:一种是用vtkObjectBase里的静态成员变量New(),用Delete()方法析构;另一种则是使用多次的智能指针vtkSmartPointer<T>。

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

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

使用New()创建的对象,如果没有用Delete()方法删除,程序有可能会出现内存泄漏,即用户负责对象内存的管理。
若使用智能指针创建对象,则无需手动调用Delete()方法减少引用计数,因为引用计数的增加与减少都是由智能指针自动完成的。使用智能指针时,首先要包含智能指针的头文件vtkSmartPointer.h。vtkSmartPointer类是一个模板类,所需的模板参数就是带创建的对象的类名,如:

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

智能指针有一个让人困惑的地方,比如当创建一个智能指针类型的对象,然后改变它的指向,这时引用计数就会出错,例如:

vtkSmartPointer<vtkImageData> imageData = vtkSmartPointer<vtkImageData>::New();
imageData = reader->GetOutput();

上面两行代码里会先创建一个imageData,并给它分配内存,接着又把imageData指向reader的输出,而不是一致指向所创建的内存。对于这种情况,只需简单地调用:

vtkImageData* imageData = reader->GetOutput();

这里没有必要使用智能指针,因为没有创建任何新的对象。

2.3 运行时的类型识别

在C++里,对象类型是通过typeid(需要包含头文件#include<type_info>)获取的;VTK里再vtkObjectBase定义了获取对象类型的方法——GetClassName()和IsA()。

GetClassName()返回的是该对象类名的字符串(VTK里用类名来识别各个对象),如

vtkSmartPoint<vtkBMPReader> reader = vtkSmartPointer<vtkBMPReader>::New();
const char* type = reader->GetClassName();	// 返回“vtkBMPReader”字符串

IsA()方法用于测试某个对象是否为指定字符串的类型或其子类类型,比如:

if(reader->IsA("vtkBMPReader")){······};	// 这里IsA()会返回真。

类比C++里的操作RTTI(Real-time type information)操作符,除了typeid之外,还有dynamic_cast,主要用于基类向子类的类型转换,称为向下转型。VTK里同样提供了类似的方法,也就是vtkObject里定义的SafeDownCast(),它是vtkObject里的静态成员函数,意味着它是属于类,而不是属于对象的,即可以用vtkObject::SafeDownCase()直接调用,比如:

vtkSmartPoint<vtkImageReader> readerBase = vtkSmartPoint<vtkImageReader>::New();
vtkBMPReader* bmpReader = vtkBMPReader::SafeDownCast(readerBase);

与dynamic_cast类似,SafeDownCast也是运行时才转换的,这种转换只有当bmpReader的类型确实是reader Base的派生类时才有效,否则返回空指针。

除了运行时类型识别,vtkObjectBase还提供了用于调试的状态输出接口Print(),其调用内部PrintSelf()、PrintHeader()、PrinrTailer()等函数实现。在调试VTK程序时,如果需要输出某个对象的状态信息时,一般都是调用Print()函数,如:

bmpReader->Print(std::cout);