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

C++ 在没有派生类的先验知识的情况下,有没有更好的方法来定义访问者模式?,c++,templates,design-patterns,C++,Templates,Design Patterns,这是我第一次遇到for。在我的场景中,从公共基类派生的对象可以以某种方式相互交互,因此访问者和访问者来自同一个集合。然而,出于实际原因,我的目标是将逻辑与应用程序完全分离。特别是,我希望不惜一切代价避免使用基类,以了解哪些派生类可以使用它。在一个应用程序中,将有不同的独立应用程序层接近相同的基础 问题是需要在基中为每个可能的派生类定义virtual方法。这可以使用模板轻松解决。一个更大的问题是,我不想被限制在特定数量(或上限)的派生类中。这是我得到的最接近的结果: /*** Generic **

这是我第一次遇到for。在我的场景中,从公共基类派生的对象可以以某种方式相互交互,因此访问者和访问者来自同一个集合。然而,出于实际原因,我的目标是将逻辑与应用程序完全分离。特别是,我希望不惜一切代价避免使用基类,以了解哪些派生类可以使用它。在一个应用程序中,将有不同的独立应用程序层接近相同的基础

问题是需要在基中为每个可能的派生类定义
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
}
< >(在代码中完全替换了<代码>访问< /Cord>模板),在任何变化中,C++都是可能的。也就是说,这不起作用,原因与以下原因大致相同:

重载解析仅选择基础模板(或非模板函数,如果有)。只有在决定要选择哪个基础模板,并且该选择被锁定之后,编译器才会四处查看该模板是否有合适的专用化,以及是否会使用该专用化

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

我试图调整的下半部分,但如果
D1
D2
需要从同一个基派生,则它将不起作用(除非所有派生的都硬编码到该基中)。当然,可以进行
dynamic_cast
。但这段代码应该每秒调用100000次,我不希望RTTI成为我的主要瓶颈

目前,我一直停留在一种中间路线上,
base
的基类被单个模板类所取代,该模板类需要按照
Visit
的思路,由每个应用程序模式分别提供,这似乎是最无害的,但我仍然很好奇。只是列出几个类的名字,并让C++为我生成几行代码真的是不可能的? 由于每个注入的
visit(X*)
都来自
InjectVisit
的不同模板实例化,因此它们不会相互竞争,从而导致歧义错误(即使在任何时候只能使用其中一个)

您可以使用以下
技巧:


我无法测试此ATM,但如果您尝试使用
x,y
版本而不是
template struct-Visit:Visit{virtual int-Visit(x*)=0;}
,该怎么办。这只是我所问的参数包建议的另一个版本(使用递归而不是扇出),失败时出现了相同的错误。如果您避免专门化,那么?@Amit抱歉,应该更清楚一些。
InjectVisit
完全取代了以前的
Visit
模板。这不是另一个专业。我会把它放在问题里。太棒了!只有
invite
方法消失了(所以现在它只是一个分派),但我能够将它放在
Base
的超类中,现在我的模式按预期工作。我能做这个小编辑吗?真的很优雅。我尝试在不同阶段使用
插入
,但没有想到在整个过程中都采用这些方法。@当然,继续编辑。这个
using
技巧用于实现我所做的更改,希望它们不会破坏您的编码或命名风格。我还将
invite
重命名为
accept
(wrt.the question),这在访问者模式中更为常见。这种方式说明了如何动态识别指向基类的指针,以在参数和
This
中指向
D1
D2
的实例,而不仅仅是在参数中。
template<class X>
struct InjectVisit {
  virtual int visit(X*) = 0;
};

template<class... Derived>
struct Base : InjectVisit<Derived>... {
  virtual int accept(Base*) = 0;
};
#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