在没有派生类的先验知识的情况下,有没有更好的方法来定义访问者模式?

时间:2016-10-06 22:42:50

标签: c++ templates design-patterns

这是我第一次遇到visitor design patterndouble 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的后半部分,但如果D1D2需要从相同的基础派生,则无效除非再次将所有派生词硬编码到那个)。当然,dynamic_cast是可能的。但是这段代码每秒被称为几十万个,我不希望RTTI成为我的主要瓶颈。

目前,我被困在一个中间路上,其中Base的基类被单个模板类所取代,需要按照Visit的行分别由每个应用程序模式提供,这似乎是最不邪恶的,但我仍然很好奇。只是列出几个类的名称,并让C ++按需生成几行,真的不可能吗?

1 个答案:

答案 0 :(得分:1)

  

由于每个注入的visit(X*)都来自InjectVisit的不同模板实例,因此它们不会相互竞争,从而导致模糊错误(即使只有其中一个可以可以在任何时候使用。)

您可以使用以下using技巧:

LIVE DEMO

#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