C++如何降低为有限CRTP特化实现虚方法的代码开销?
我尝试将CRTP类型参数传入虚方法,这就要求虚方法为模板类型,但C目前暂不支持该特性:因为编译器实现动态调度通常依赖vtable(虚函数表),模板虚方法会导致vtable大小直到所有源码编译完成进入链接阶段才能确定,不符合C的编译规则。
但在我的具体场景中,CRTP的特化数量是有限且已知的,因此可以为每个特化单独定义虚方法重载,再在子类中重写这些方法。我准备了一个最小可复现示例来展示我的场景,首先是如下的CRTP继承体系:
template<typename Actual> struct CRTPBase { using actual_type = Actual; void foo() { static_cast<actual_type*>(this)->foo(); } int bar(int i) const { return static_cast<const actual_type*>(this)->bar(i); } }; struct A : CRTPBase<A> { void foo() { /* 实现A的foo逻辑 */ } int bar(int i) const { return i + 1; } }; struct B : CRTPBase<B> { void foo() { /* 实现B的foo逻辑 */ } int bar(int i) const { return i - 1; } };
接下来我需要定义一个虚类继承体系,通过虚方法处理所有CRTPBase<T>的特化。因为我知晓所有的特化类型,我可以按如下方式实现:
struct VirtualBase { virtual ~VirtualBase() { } virtual void accept_crtp(const CRTPBase<A> &o) = 0; virtual void accept_crtp(const CRTPBase<B> &o) = 0; }; struct VirtualDerived : VirtualBase { void accept_crtp(const CRTPBase<A> &o) override { /* 处理A的大量逻辑 */ } void accept_crtp(const CRTPBase<B> &o) override { /* 处理B的同类逻辑 */ } };
可以看到,CRTPBase<T>的每个特化都需要在纯虚基类和所有派生类中对应一个虚方法,随着CRTPBase<T>特化数量和VirtualBase派生类数量的增加,这部分代码开销会快速膨胀。
我理想的实现方式大致如下:
struct VirtualBase { virtual ~VirtualBase() { } template<typename T> virtual void accept_crtp(const CRTPBase<T> &o) = 0; }; struct VirtualDerived : VirtualBase { template<typename T> void accept_crtp(const CRTPBase<T> &o) override { /* 一套逻辑处理所有CRTPBase<T>特化 */ } };
但出于开头提到的原因,这种写法无法通过编译。也有其他开发者遇到过相同的问题,他们提出的方案是:仍然为每个特化声明定义虚方法,但在派生类中将实际逻辑封装为非虚模板方法,所有虚方法重载都转发调用这个模板方法:
struct VirtualBase { virtual ~VirtualBase() { } virtual void accept_crtp(const CRTPBase<A> &o) = 0; virtual void accept_crtp(const CRTPBase<B> &o) = 0; }; struct VirtualDerived : VirtualBase { void accept_crtp(const CRTPBase<A> &o) override { accept_any_crtp(o); } void accept_crtp(const CRTPBase<B> &o) override { accept_any_crtp(o); } private: template<typename T> void accept_any_crtp(const CRTPBase<T> &o) { /* 一套逻辑处理所有CRTPBase<T>特化 */ } };
这种方案虽然避免了处理逻辑的代码重复,但仍然需要在虚基类和所有派生类中为每个特化手动编写对应的方法声明和转发逻辑。
我的问题是:如何降低这部分实现开销?
我曾考虑使用如下形式的X宏来批量生成虚基类和派生类中的方法:
#define CRTP_SPECIALIZATIONS_LIST(X) X(A) X(B) // 列出所有CRTP特化,此处为A和B
但该方案的问题是,如果CRTP体系定义在CRTP.hpp中,而虚基类和派生类在其他源文件中声明/定义,这个宏会“泄漏”到所有包含该头文件的翻译单元中。有没有更优雅的解决方式?是否可以通过模板手段,比如可变参数模板来实现相同的目标?
内容的提问来源于stack exchange,提问作者Immanuel Haffner

