现有的两种解决方案用动态多态性换取静态多态性。如果没有关于手头问题的更多细节,就不可能知道这是否是一种有效的方法,因为它基本上打破了多态层次结构:CRTP 没有单一的基类,而是它们的一个家族。您不能将对象Derived1
和对象保存Derived2
在同一个容器中,因为它们是不相关的……如果您只需要共享代码,这是一个很好的解决方案,但如果您需要动态多态性则不是。看看访客模式和类似问题的双重调度。
如果您需要动态多态性,您可以尝试实现双重调度(这很痛苦,但如果层次结构足够小,则可行。基本上创建两个不同的层次结构,一个植根于Base
,另一个用作手动调度器的一部分。层次结构rooted atBase
将有一个虚拟方法apply
,第二个层次结构将为第一个层次结构中的每个类型提供虚拟函数:
class Base;
class Derived1; // inherits from Base, implements Visitor
class Derived2; // inherits from either Base or Derived2
struct Visitor {
virtual void visit( Base& ) = 0; // manually unrolled for all types
virtual void visit( Derived1& ) = 0;
virtual void visit( Derived2& ) = 0;
};
struct Base {
virtual void apply( Visitor& v ) { // manually replicate this in Derived1, 2
v.visit( *this );
}
template <typename T> void foo(T); // implement
};
template <typename T>
struct FooCaller : Visitor {
T& ref_value;
FooCaller( T& v ) : ref_value(v) {}
template <typename U> void call_foo( U& o ) {
o.foo(ref_value);
}
virtual void visit( Base & b ) { call_foo(b); }
virtual void visit( Derived1 & d1 ) { call_foo(d1); }
virtual void visit( Derived2 & d2 ) { call_foo(d2); }
};
我用的名字在Visitor模式中很常见,这种做法和那个模式很像(我不敢称它为Visitor模式,但做法大同小异,所以我只是借用了命名约定)。
用户代码类似于:
int main() // main returns int, not void!!!
{
Base* BasePtr = new Derived1();
int i = 5;
FooCaller<int> c(i)
BasePtr->apply(c); // [1] magic happens here
}
通过将函数的参数(如果可能)从引用更改为 const 引用,可以释放声明i
和事先声明的要求。c
真正的魔力在于,在 [1] 中,C++ 单一分派机制仍然分派对 的调用Derived1::apply
,因为这是 . 指向的对象的动态类型BasePtr
。那时它会Visitor::visit( Derived1& )
以自己作为参数调用。这将再次通过单一分派机制分派到FooCaller<int>::visit( Derived1& )
,此时两个对象都已解析为它们的静态类型。当FooCaller<int>::visit
调用时call_foo
,参数U
被推断为 be Derived1
,当它调用时Derived1::foo
,参数被推断为 beint
并且它最终调用Derived1::foo<int>
......尽管有几个循环和间接......
根据您的特定用例,这可能太复杂(如果像 CRTP 这样的静态多态性可以工作)或太难维护(如果层次结构很大:对于层次结构中的每个新元素,Base
您必须更新层次结构中的所有类型Visitor
),所以如果你能避免这种复杂性,那就完美了。但是,在某些情况下,您需要这个。
另请注意,这是最复杂的全动态解决方案,介于两者之间还有其他选项,具体取决于您需要什么是运行时多态性......这可能是您的层次结构为短裤访问者建模的情况,并且您只需要手动展开将在内部分派到模板的不同虚函数,在这种情况下,上述复杂性的一半将消失。
另请注意,这在 C++ 中非常不寻常,如果您解释手头的实际问题,可能会有更好的更简单的解决方案,您所说的是原始问题的解决方案的要求:动态分派到模板。