VTK 设计模式:工厂模式
系列:Qt / VTK 设计模式 · VTK 07/10
参考:vtkObjectFactory、vtkObject.h New() macro
引子
在 VTK 中,你几乎永远看不到裸写 new vtkPolyData() 或 new vtkXMLPolyDataReader()。所有的 VTK 对象创建必须统一通过静态宏 ClassName::New() 来实例化。
这正是**工厂模式(Factory Pattern)**与 注册式工厂(Registered Factory) 在 VTK 中的深远落地:提供创建对象的统一接口,由工厂决定实例化哪一个类,从而允许在运行时动态替换底层实现(例如利用插件将标准 vtkPolyData 替换为加速版的 GPU 物理对象)。
核心痛点与 GoF 工厂结构
痛点场景:直接使用 C++ 原生 new 的缺陷
cpp
// 坏方案:直接裸写 C++ new
vtkPolyData* pd = new vtkPolyData(); // 绕过了 VTK 的引用计数系统与工厂重写链- 破坏内存引用计数规则:VTK 对象的销毁依赖
Register()/UnRegister()(或Delete()),原生new未经过正确的工厂初始化。 - 无法插件化重写 (Override):第三方 OEM 无法在不修改开源代码的前提下将某个核心类全局替换为硬件加速实现。
GoF 工厂模式结构映射
| GoF 工厂角色 | VTK 对应实现 | 职责说明 |
|---|---|---|
| 抽象产品 (Product) | vtkObject / vtkAbstractPolyDataReader | 定义对象的统一基类接口 |
| 具体产品 (ConcreteProduct) | vtkPolyData / vtkSTLReader | 物理内存中构造的具体数据或算法对象 |
| 工厂方法 (Factory Method) | ClassName::New() | 静态工厂方法,拦截创建动作并触发工厂覆盖链 |
| 注册式工厂 (Factory Registry) | vtkObjectFactory / vtkObjectFactoryRegistry | 管理动态插件注册表,根据类名字符串匹配并实例化替代对象 |
底层原理:vtkObjectFactory 替换链流程
调用 vtkPolyData::New() 时,VTK 底层并非直接执行 new vtkPolyData,而是先向 vtkObjectFactory 询问是否有被注册覆盖:
- 注册覆盖:第三方开发者继承
vtkObjectFactory并实现CreateObject(),通过静态加载或动态库导出注册。 - 零入侵替换:原现有的全套上层业务代码无需修改任何一行
New()调用,自动获得性能提升。
完整代码示例
1. 标准工厂实例化与 RAII 智能指针
cpp
#include <vtkNew.h>
#include <vtkSmartPointer.h>
#include <vtkSphereSource.h>
#include <vtkPolyData.h>
#include <iostream>
void standardFactoryUsage() {
// 1. 使用 vtkNew 工厂宏(自动在栈上分配包装,离开作用域自动 UnRegister 释放)
vtkNew<vtkSphereSource> sphere;
sphere->SetRadius(5.0);
sphere->Update();
// 2. 使用 vtkSmartPointer 工厂智能指针
vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
polyData->ShallowCopy(sphere->GetOutput());
std::cout << "PolyData Points Count: " << polyData->GetNumberOfPoints() << std::endl;
}2. 自定义注册式工厂重写 (vtkObjectFactory 子类)
cpp
#include <vtkObjectFactory.h>
#include <vtkPolyData.h>
#include <iostream>
// 自定义派生类
class CustomPolyData : public vtkPolyData {
public:
static CustomPolyData* New();
vtkTypeMacro(CustomPolyData, vtkPolyData);
void PrintSelf(std::ostream& os, vtkIndent indent) override {
os << indent << "CustomPolyData (Plugin Override Active)\n";
vtkPolyData::PrintSelf(os, indent);
}
};
vtkStandardNewMacro(CustomPolyData);
// 自定义工厂类,用于覆盖默认 vtkPolyData 的 New() 行为
class CustomPolyDataFactory : public vtkObjectFactory {
public:
static CustomPolyDataFactory* New();
vtkTypeMacro(CustomPolyDataFactory, vtkObjectFactory);
const char* GetVTKSourceVersion() override { return VTK_SOURCE_VERSION; }
const char* GetDescription() override { return "Custom PolyData Factory Override"; }
protected:
CustomPolyDataFactory() {
// 注册覆盖关系:将 "vtkPolyData" 映射为 "CustomPolyData"
this->RegisterOverride("vtkPolyData",
"CustomPolyData",
"Custom Override for PolyData",
1,
CreateCustomPolyData);
}
static vtkObject* CreateCustomPolyData() {
return CustomPolyData::New();
}
};
vtkStandardNewMacro(CustomPolyDataFactory);
// 测试注册式工厂生效
int main() {
// 1. 注册自定义工厂
vtkNew<CustomPolyDataFactory> factory;
vtkObjectFactory::RegisterFactory(factory);
// 2. 调用标准 New(),底层自动被替换为 CustomPolyData
vtkSmartPointer<vtkPolyData> pd = vtkSmartPointer<vtkPolyData>::New();
// 打印类型名称,输出 CustomPolyData
std::cout << "Class Name: " << pd->GetClassName() << std::endl;
return 0;
}易混淆概念对比
| 创建方式 | 原理与性质 | 适用场景 |
|---|---|---|
ClassName::New() | 走 vtkObjectFactory 注册覆盖链,分配堆内存,ReferenceCount = 1 | 所有 VTK 对象的标准创建范式 |
vtkNew<T> | 栈上自动包裹的 RAII 句柄,不允许指针拷贝赋予 | 函数内部局部变量生命周期管理 |
vtkSmartPointer<T>::New() | 引用计数智能指针,支持赋值与传参转移 | 作为成员变量、数据结构存储或跨函数传递 |
最佳实践与踩坑避坑
- 严禁混用 C++ 原生
delete:- 凡是通过
New()创建的 VTK 对象,生命周期一律由引用计数控制。手动delete obj会破坏 VTK 内部引用计数机制,导致严重的内存双重释放崩溃!正确手动销毁方式是obj->Delete()(或直接使用vtkSmartPointer)。
- 凡是通过
- 跨 DLL 模块的工厂注册:
- 如果自定义的
vtkObjectFactory存在于独立的动态链接库(.dll / .so)中,需确保在main()入口显式调用一次注册函数,或配置环境环境变量VTK_AUTOLOAD_PATH自动加载插件 DLL。
- 如果自定义的
重点与注意
重点:VTK 对象的唯一标准创建姿势是
ClassName::New();切勿裸写 C++new。
重点:vtkObjectFactory提供了无缝的全局插件重写机制(基类New()动态返回派生类对象)。
注意:vtkNew在栈上分配,只在局部作用域生效;跨函数传参或存入容器请使用vtkSmartPointer。
小结
VTK 工厂模式将内存引用计数初始化与物理类型解耦结合在一起,为庞大的可视化库提供了灵活的动态扩展接口。
延伸阅读