避免代码重复:跨库属性编辑对话框的模板+虚函数替代方案求解
问题
我有多个带不同属性的对象,想要实现一个属性编辑对话框,能通过模板传递对象属性并基于模板构建对应对象的专属对话框面板。但C++不支持虚函数与模板混合使用,请问该如何传递对象属性并返回修改后的属性集,以此更新对象的视觉表现?
我试过传递对象ID、用长switch语句构建面板,但这种实现不够优雅。更复杂的是,属性对话框与对象分属不同库,我想尽可能分离代码、打破链接依赖。
相关代码示例(已修正原语法错误):
class A { private: std::string name, tag; font labelFont, textFont; }; class B { private: std::string name, tag; color labelColor, textColor; }; // 原代码typedef模板写法不符合C++语法,此处修正为标准模板声明 template<typename T> int CallDialog(window *parent, T &options, int objectType); int CallDialog(window *parent, void* options, int objectType) { MyDialog dlg(parent, options, objectType); if (dlg.OK()) { if (objectType == 1) { static_cast<A*>(options)->labelFont = dlg.GetLabelFont(); static_cast<A*>(options)->textFont = dlg.GetTextFont(); } // 其他类型的case分支... } return 0; }
方案1:类型擦除(Type Erasure)
核心是用非模板抽象接口封装模板实现,既避开虚函数与模板的冲突,又实现跨库的类型无关调用,彻底解耦对话框库与具体对象类型。
实现步骤:
- 对话框库定义抽象接口(仅暴露接口,不依赖业务对象):
// 对话框库头文件 class IPropertyEditor { public: virtual ~IPropertyEditor() = default; // 填充对话框控件初始值 virtual void PopulateDialog(MyDialog& dlg) = 0; // 将对话框修改值应用到对象 virtual void ApplyChanges(MyDialog& dlg) = 0; };
- 业务库实现模板子类(绑定具体对象的属性操作):
// 业务库代码 template<typename T> class PropertyEditorImpl : public IPropertyEditor { public: PropertyEditorImpl(T& obj) : m_obj(obj) {} void PopulateDialog(MyDialog& dlg) override { if constexpr (std::is_same_v<T, A>) { dlg.SetLabelFont(m_obj.labelFont); dlg.SetTextFont(m_obj.textFont); } else if constexpr (std::is_same_v<T, B>) { dlg.SetLabelColor(m_obj.labelColor); dlg.SetTextColor(m_obj.textColor); } // 扩展其他对象类型... } void ApplyChanges(MyDialog& dlg) override { if constexpr (std::is_same_v<T, A>) { m_obj.labelFont = dlg.GetLabelFont(); m_obj.textFont = dlg.GetTextFont(); } else if constexpr (std::is_same_v<T, B>) { m_obj.labelColor = dlg.GetLabelColor(); m_obj.textColor = dlg.GetTextColor(); } // 扩展其他对象类型... } private: T& m_obj; };
- 对话框库修改调用函数(仅依赖抽象接口):
// 对话框库实现 int CallDialog(window* parent, IPropertyEditor* editor) { MyDialog dlg(parent); editor->PopulateDialog(dlg); if (dlg.OK()) { editor->ApplyChanges(dlg); return 1; // 返回1表示修改成功 } return 0; }
- 业务侧调用方式:
A objA; PropertyEditorImpl<A> editorA(objA); CallDialog(parentWindow, &editorA); B objB; PropertyEditorImpl<B> editorB(objB); CallDialog(parentWindow, &editorB);
方案2:回调函数(Callback)
通过传递填充对话框、应用修改的回调函数,实现类型无关调用,同样能解耦库依赖,且代码更轻量化。
实现步骤:
- 对话框库定义回调类型与调用函数:
// 对话框库头文件 using PopulateCallback = void(*)(MyDialog&, void*); using ApplyCallback = void(*)(MyDialog&, void*); int CallDialog(window* parent, void* obj, PopulateCallback populate, ApplyCallback apply) { MyDialog dlg(parent); populate(dlg, obj); if (dlg.OK()) { apply(dlg, obj); return 1; } return 0; }
- 业务侧为每个对象类型实现回调:
// 业务库代码 void PopulateA(MyDialog& dlg, void* obj) { A* a = static_cast<A*>(obj); dlg.SetLabelFont(a->labelFont); dlg.SetTextFont(a->textFont); } void ApplyA(MyDialog& dlg, void* obj) { A* a = static_cast<A*>(obj); a->labelFont = dlg.GetLabelFont(); a->textFont = dlg.GetTextFont(); } void PopulateB(MyDialog& dlg, void* obj) { B* b = static_cast<B*>(obj); dlg.SetLabelColor(b->labelColor); dlg.SetTextColor(b->textColor); } void ApplyB(MyDialog& dlg, void* obj) { B* b = static_cast<B*>(obj); b->labelColor = dlg.GetLabelColor(); b->textColor = dlg.GetTextColor(); }
- 业务侧调用方式:
A objA; CallDialog(parentWindow, &objA, PopulateA, ApplyA); B objB; CallDialog(parentWindow, &objB, PopulateB, ApplyB);
方案3:CRTP(奇异递归模板模式)
如果允许业务库依赖对话框库的模板接口,可以用CRTP实现静态多态,避免虚函数开销,同时兼顾模板与多态的特性。
实现步骤:
- 对话框库定义CRTP基类:
// 对话框库头文件 template<typename Derived> class PropertyEditor { public: int ShowDialog(window* parent) { MyDialog dlg(parent); // 静态多态调用子类实现 static_cast<Derived*>(this)->PopulateDialog(dlg); if (dlg.OK()) { static_cast<Derived*>(this)->ApplyChanges(dlg); return 1; } return 0; } };
- 业务侧实现具体编辑器:
// 业务库代码 class AEditor : public PropertyEditor<AEditor> { public: AEditor(A& obj) : m_obj(obj) {} void PopulateDialog(MyDialog& dlg) { dlg.SetLabelFont(m_obj.labelFont); dlg.SetTextFont(m_obj.textFont); } void ApplyChanges(MyDialog& dlg) { m_obj.labelFont = dlg.GetLabelFont(); m_obj.textFont = dlg.GetTextFont(); } private: A& m_obj; }; class BEditor : public PropertyEditor<BEditor> { public: BEditor(B& obj) : m_obj(obj) {} void PopulateDialog(MyDialog& dlg) { dlg.SetLabelColor(m_obj.labelColor); dlg.SetTextColor(m_obj.textColor); } void ApplyChanges(MyDialog& dlg) { m_obj.labelColor = dlg.GetLabelColor(); m_obj.textColor = dlg.GetTextColor(); } private: B& m_obj; };
- 业务侧调用方式:
A objA; AEditor editorA(objA); editorA.ShowDialog(parentWindow); B objB; BEditor editorB(objB); editorB.ShowDialog(parentWindow);
内容的提问来源于stack exchange,提问作者Igor
相关产品推荐
相关产品推荐

