带模板函数的C++类接口设计:非模板Print多态实现方案
问题与解决方案
问题描述
我有一段简化后的代码(实际逻辑更复杂):
class IFoo { public: template <typename T> T GetInc(T x) const { return x; } }; class Foo : public IFoo { public: template <typename T> T GetInc(T x) const { return x + 1; } }; class Bar : public IFoo { public: template <typename T> T GetInc(T x) const { return x + 2; } }; void Print(const IFoo& foo) { std::cout << foo.GetInc(1) << std::endl; std::cout << foo.GetInc(3.14) << std::endl; } int main() { Foo foo; Print(foo); Bar bar; Print(bar); return 0; }
这段代码输出不符合预期——模板函数无法被多态调用,Print中始终执行的是基类IFoo::GetInc的逻辑。
我尝试过CRTP模式,但这会迫使Print函数模板化,而大量调用点会因此增加代码复杂度,这是我不想看到的:
template <typename T> class IFoo { public: template <typename U> U GetInc(U x) const { return static_cast<const T *>(this)->GetInc(x); } }; class Foo : public IFoo<Foo> { public: template <typename T> T GetInc(T x) const { return x + 1; } }; class Bar : public IFoo<Bar> { public: template <typename T> T GetInc(T x) const { return x + 2; } }; template <typename T> void Print(const IFoo<T> &foo) { std::cout << foo.GetInc(1) << std::endl; std::cout << foo.GetInc(3.14) << std::endl; }
NVI模式也试过,但基类无法调用派生类的模板函数。我的核心需求是:为带有模板函数的类创建统一接口,同时避免将调用该接口的函数(如Print)模板化。
解决方案
方案一:针对已知类型提供虚函数接口
如果GetInc只需要处理有限的、预先确定的类型(比如int、double),可以在基类中为这些类型定义纯虚函数,派生类实现具体逻辑,同时保留模板函数作为统一调用入口:
#include <iostream> #include <type_traits> class IFoo { public: // 为需要支持的类型定义纯虚函数 virtual int GetInc(int x) const = 0; virtual double GetInc(double x) const = 0; // 模板函数作为统一调用入口,转发到对应类型的虚函数 template <typename T> T GetInc(T x) const { if constexpr (std::is_same_v<T, int>) { return GetInc(static_cast<int>(x)); } else if constexpr (std::is_same_v<T, double>) { return GetInc(static_cast<double>(x)); } else { // 限制不支持的类型,避免误用 static_assert(std::is_void_v<T>, "Unsupported type for GetInc"); } } virtual ~IFoo() = default; }; class Foo : public IFoo { public: int GetInc(int x) const override { return x + 1; } double GetInc(double x) const override { return x + 1; } // 可选:保留派生类的模板函数,或直接使用基类的模板函数 template <typename T> T GetInc(T x) const { return IFoo::GetInc(x); } }; class Bar : public IFoo { public: int GetInc(int x) const override { return x + 2; } double GetInc(double x) const override { return x + 2; } template <typename T> T GetInc(T x) const { return IFoo::GetInc(x); } }; void Print(const IFoo& foo) { std::cout << foo.GetInc(1) << std::endl; std::cout << foo.GetInc(3.14) << std::endl; } int main() { Foo foo; Print(foo); // 输出2和4.14 Bar bar; Print(bar); // 输出3和5.14 return 0; }
优点:Print无需模板化,完全符合需求;实现简单,无额外运行时开销。
缺点:仅支持预先定义的类型,扩展性有限。
方案二:使用类型擦除(Type Erasure)
如果需要支持任意类型的T,可以通过类型擦除技术封装模板函数的行为,让基类通过虚函数间接调用派生类的模板方法:
#include <iostream> #include <memory> #include <unordered_map> #include <stdexcept> // 类型擦除的基类,封装GetInc的行为 template <typename T> class IncImpl { public: virtual T operator()(T x) const = 0; virtual ~IncImpl() = default; }; // 包装派生类的模板函数实现 template <typename Derived, typename T> class IncImplWrapper : public IncImpl<T> { public: explicit IncImplWrapper(const Derived& derived) : derived_(derived) {} T operator()(T x) const override { return derived_.GetInc(x); } private: const Derived& derived_; }; class IFoo { public: template <typename T> T GetInc(T x) const { auto type_hash = typeid(T).hash_code(); auto it = impls_.find(type_hash); if (it != impls_.end()) { // 转换到对应类型的实现并调用 auto impl = static_cast<const IncImpl<T>*>(it->second.get()); return (*impl)(x); } throw std::runtime_error("Unsupported type for GetInc"); } protected: // 派生类构造时注册需要支持的类型 template <typename Derived> void registerImpl() { // 注册需要支持的类型,可扩展 registerType<Derived, int>(); registerType<Derived, double>(); // 添加更多类型... } private: template <typename Derived, typename T> void registerType() { auto type_hash = typeid(T).hash_code(); impls_[type_hash] = std::make_unique<IncImplWrapper<Derived, T>>(*static_cast<const Derived*>(this)); } // 存储不同类型的擦除实现 std::unordered_map<size_t, std::unique_ptr<void>> impls_; }; class Foo : public IFoo { public: Foo() { registerImpl<Foo>(); } template <typename T> T GetInc(T x) const { return x + 1; } }; class Bar : public IFoo { public: Bar() { registerImpl<Bar>(); } template <typename T> T GetInc(T x) const { return x + 2; } }; void Print(const IFoo& foo) { std::cout << foo.GetInc(1) << std::endl; std::cout << foo.GetInc(3.14) << std::endl; } int main() { Foo foo; Print(foo); // 输出2和4.14 Bar bar; Print(bar); // 输出3和5.14 return 0; }
优点:支持任意预先注册的类型,Print无需模板化;扩展性较好。
缺点:需要额外的类型擦除逻辑,存在一定运行时开销;派生类构造时需手动注册支持的类型。
内容的提问来源于stack exchange,提问作者edmond
相关产品推荐
相关产品推荐

