寻求支持用户自定义实现的灵活通用序列化设计模式
解决方案:让用户完全控制序列化逻辑的C++17兼容设计
核心问题分析
原设计依赖全局serialize模板函数,用户无法直接替换为自定义的magic_serialize——因为两个模板函数无特化优先级,无法通过特化覆盖全局实现。我们需要将序列化逻辑从硬编码的全局函数改为可注入的策略,同时尽量保持原有API的兼容性。
方案1:模板参数注入序列化策略(低侵入,兼容C++17)
这个方案仅修改impl::Derived和DerivedHolder的模板定义,不改动DAGNode的核心抽象,完全兼容原有代码,同时允许用户传入自定义序列化逻辑。
步骤1:修改库内部实现
1.1 调整impl::Derived模板,增加序列化策略参数
namespace impl { // 不属于库API,新增SerializeStrategy模板参数,默认使用全局serialize template <typename T, typename SerializeStrategy = decltype(&::serialize<T>)> class Derived : public DAGNode { public: // 构造函数新增策略参数,默认绑定全局serialize Derived(T const& t, SerializeStrategy strategy = ::serialize<T>) : m_value(t), m_serialize_strategy(strategy) {} std::string serialize() const override final { // 调用注入的策略函数 return m_serialize_strategy(m_value); } void add_parent(NodePtr const& ptr) { m_parents.push_back(ptr); } private: T m_value; SerializeStrategy m_serialize_strategy; }; } // namespace impl
1.2 同步修改DerivedHolder模板,传递策略参数
// 新增SerializeStrategy模板参数,默认保持兼容 template <typename T, typename SerializeStrategy = decltype(&::serialize<T>)> class DerivedHolder { public: // 构造函数允许用户传入自定义策略,默认用全局serialize DerivedHolder(T const& t, SerializeStrategy strategy = ::serialize<T>) : m_node(std::make_shared<impl::Derived<T, SerializeStrategy>>(t, strategy)) {} NodePtr holder() const { return m_node; } void add_parent(NodePtr const& p) { m_node->add_parent(p); } private: std::shared_ptr<impl::Derived<T, SerializeStrategy>> m_node; };
步骤2:用户侧使用方式
方式1:针对单个Holder传入自定义策略
// 用户的自定义序列化函数 template <typename T> std::string magic_serialize(T const& t) { if constexpr (std::is_same_v<T, MyMagicClass> ) { return t.as_string(); } else { MagicClass m(t); return m.as_string(); } } // 创建Holder时传入magic_serialize DerivedHolder<MyMagicClass, decltype(&magic_serialize<MyMagicClass>)> holder(my_obj, magic_serialize<MyMagicClass>);
方式2:全局默认替换(通过模板别名简化)
如果用户想全局使用magic_serialize,可以定义别名避免重复写模板参数:
template <typename T> using MagicDerivedHolder = DerivedHolder<T, decltype(&magic_serialize<T>)>; // 使用时直接用别名,自动绑定magic_serialize MagicDerivedHolder<MyMagicClass> holder(my_obj); MagicDerivedHolder<int> int_holder(42);
方案2:基于ADL的序列化(更灵活,彻底解耦)
如果希望完全摆脱全局函数的依赖,可以改用参数依赖查找(ADL),让序列化逻辑与类型绑定,用户只需在类型所在命名空间定义serialize函数即可。
修改库内部实现
1. 移除全局serialize模板,修改impl::Derived的序列化调用
namespace impl { template <typename T> class Derived : public DAGNode { public: Derived(T const& t) : m_value(t) {} std::string serialize() const override final { // 利用ADL查找T所在命名空间的serialize函数 using std::to_string; // 可选:内置类型回退到std::to_string return serialize(m_value); } void add_parent(NodePtr const& ptr) { m_parents.push_back(ptr); } private: T m_value; }; } // namespace impl
2. DerivedHolder无需修改,保持原有API
用户侧使用方式
用户只需在自己类型的命名空间定义serialize:
namespace my_ns { class MyMagicClass { /* ... */ }; // 为MyMagicClass定义序列化函数 std::string serialize(const MyMagicClass& t) { return t.as_string(); } // 为其他类型定义 std::string serialize(int val) { MagicClass m(val); return m.as_string(); } } // namespace my_ns // 使用时自动匹配对应序列化函数 DerivedHolder<my_ns::MyMagicClass> holder(my_obj); DerivedHolder<int> int_holder(42);
方案对比
| 方案 | 侵入性 | 灵活性 | 兼容性 |
|---|---|---|---|
| 模板参数注入 | 低(仅修改两个模板) | 高(支持全局/局部替换) | 完全兼容原有代码 |
| ADL序列化 | 中(移除全局serialize) | 极高(完全解耦,符合C++惯例) | 原有用户需将特化改为ADL函数,需少量适配 |
推荐优先使用模板参数注入方案,因为它完全兼容现有用户代码,同时给新用户提供了灵活的自定义入口。
内容的提问来源于stack exchange,提问作者joergbrech
相关产品推荐
相关产品推荐

