这是我第一次遇到visitor design pattern的double dispatch。在我的场景中,从公共基类派生的对象可以以某种方式相互交互,因此访问者和被访者来自同一个集合。但是,出于实际原因,我的目标是将逻辑与应用程序完全分开。特别是,我想避免不惜一切代价基类,以了解哪些派生类可以使用它。在一个应用程序中将有不同的独立应用程序层接近相同的基础。
问题是需要在基础中为每个可能的派生类定义virtual
方法。这可以使用模板轻松解决。更大的问题是我不希望被限制在派生类的特定数字(或上限)。这跟我一样接近:
/*** Generic ***/
template<class...>
struct Visit { };
template<class... Derived>
struct Base : Visit<Derived...> {
virtual int accept(Base*) = 0;
};
// specializations for different numbers...
template<class X>
struct Visit<X> {
virtual int visit(X*) = 0;
};
template<class X, class Y>
struct Visit<X, Y> {
virtual int visit(X*) = 0;
virtual int visit(Y*) = 0;
};
// and more for 3, 4, ...
/*** Application ***/
struct D1;
struct D2;
using A = Base<D1, D2>; // <--- the goal is to keep this simple
struct D1 : A {
int accept(A* a) { return a->visit(this); }
int visit(D1*) { return 1; }
int visit(D2*) { return 2; }
};
struct D2 : A {
int accept(A* a) { return a->visit(this); }
int visit(D1*) { return 3; }
int visit(D2*) { return 4; }
};
int main() {
A* d1 = new D1();
A* d2 = new D2();
return d2->accept(d1); // expected: 2
}
除了最后一个标准之外,它的工作和满足大多数标准。需要提前知道可能的派生类的最大数量,并在Visit
模板中进行硬编码。并且用不同的行数重复相同的样板并不是很优雅。
我想知道是否有更清洁的东西
template<class X>
struct InjectVisit {
virtual int visit(X*) = 0;
};
template<class... Derived>
struct Base : InjectVisit<Derived>... {
virtual int accept(Base*) = 0;
};
在C ++中,(完全替换Visit
模板)在任何变体中都是可能的。这就是不起作用,原因与partial specialization of function templates won't:
重载分辨率仅选择基本模板(或非模板函数,如果有的话)。只有在确定要选择哪个基本模板并且锁定了该选项之后,编译器才会查看是否恰好可以使用该模板的合适专用,如果是,则将使用专门化。
由于每个注入的visit(X*)
来自InjectVisit
的不同模板实例,它们不会相互竞争,导致模糊错误(即使只能使用其中一个)在任何时候)。
我尝试调整this answer的后半部分,但如果D1
和D2
需要从相同的基础派生,则无效除非再次将所有派生词硬编码到那个)。当然,dynamic_cast
是可能的。但是这段代码每秒被称为几十万个,我不希望RTTI成为我的主要瓶颈。
目前,我被困在一个中间路上,其中Base
的基类被单个模板类所取代,需要按照Visit
的行分别由每个应用程序模式提供,这似乎是最不邪恶的,但我仍然很好奇。只是列出几个类的名称,并让C ++按需生成几行,真的不可能吗?
答案 0 :(得分:1)
由于每个注入的
visit(X*)
都来自InjectVisit
的不同模板实例,因此它们不会相互竞争,从而导致模糊错误(即使只有其中一个可以可以在任何时候使用。)
您可以使用以下using
技巧:
#include <iostream>
void println(const char *s)
{
using namespace std;
cout << s << endl;
}
template<typename X>
struct InjectVisit
{
virtual void visit(X*) = 0;
};
template<typename Head, typename ...Tail>
struct VirtualChain : InjectVisit<Head>, VirtualChain<Tail...>
{
using InjectVisit<Head>::visit;
using VirtualChain<Tail...>::visit;
};
template<typename Head>
struct VirtualChain<Head> : InjectVisit<Head>
{
using InjectVisit<Head>::visit;
};
template<typename ...List>
struct Base : VirtualChain<List...>
{
virtual void accept(Base*) = 0;
};
/****************************************************************/
struct D1;
struct D2;
using ConcreteBase = Base<D1, D2>;
struct D1 : ConcreteBase
{
virtual void accept(ConcreteBase* visitor) { visitor->visit(this); }
virtual void visit(D1*) { println("D1 visited by D1"); }
virtual void visit(D2*) { println("D2 visited by D1"); }
};
struct D2 : ConcreteBase
{
virtual void accept(ConcreteBase* visitor) { visitor->visit(this); }
virtual void visit(D1*) { println("D1 visited by D2"); }
virtual void visit(D2*) { println("D2 visited by D2"); }
};
int main()
{
ConcreteBase* d1 = new D1();
ConcreteBase* d2 = new D2();
d1->accept(d2);
d2->accept(d2);
}
输出是:
D1 visited by D2
D2 visited by D2