如何为C ++ 1x中的类层次结构实现完全通用的Visitor?

时间:2019-04-13 15:59:24

标签: c++ templates design-patterns metaprogramming visitor

我想使用> = C ++ 14通过模板元编程实现完全通用的Visitor模式。我已经找到了一种很好的方式来概括Visitor本身,但是在定义Visitables时遇到了麻烦。下面的代码可以工作,但是我希望main中注释掉的代码也可以工作;特别是,我希望能够有一个Visitables集合并将一个Visitor应用于每个元素。

我正在尝试用C ++做到吗?

我尝试过的事情:

  • class X : public Visitable<X>
    这解决了以下问题:没有合适的accept方法 X,但会导致编译器产生歧义X/AX/B 无法解决。
  • accept中的
  • X方法,无需继承;可以,但是 从未调用acceptA中的专用B方法。
  • 用具有功能的常规类替换模板类Visitor 任意类型的模板visit;并没有真正改变 语义,但恕我直言可读性不佳
#include <iostream>
#include <vector>

template <typename I>
class Visitable {
 public:
  template <typename Visitor>
  void accept(Visitor&& v) const {
    v.visit(static_cast<const I&>(*this));
  }
};

template <typename T, typename... Ts>
class Visitor : public Visitor<Ts...> {
 public:
  virtual void visit(const T& t);
};

template<typename T>
class Visitor<T> {
 public:
  virtual void visit(const T& t);
};

struct X {
  // template <typename V> void accept(V&& v) const {};
};

struct A : public X, public Visitable<A> {};
struct B : public X, public Visitable<B> {};

class MyVisitor : public Visitor<A, B> {
 public:
  void visit(const A& a) override { std::cout << "Visiting A" << std::endl; }
  void visit(const B& b) override { std::cout << "Visiting B" << std::endl; }
};

int main() {
  MyVisitor v {};
  // std::vector<X> elems { A(), B() };
  // for (const auto& x : elems) {
  //  x.accept(v);
  // }
  A().accept(v);
  B().accept(v);
}

2 个答案:

答案 0 :(得分:0)

*p=*p+1; // this increments the value
*p++; //while this does not

但是请注意,此处的调度是静态的。您的向量包含struct empty_t{}; template <class I, class B=empty_t> class Visitable:public B { public: // ... struct X : Visitable<X>{ }; struct A : Visitable<A,X> {}; struct B : Visitable<B,X> {}; 个而不是X个或A个。

您可能想要

B

距离越来越近。

template <class Visitor>
struct IVisitable {
  virtual void accept(Visitor const& v) const = 0;
protected:
  ~IVisitable(){}
};

template <class I, class Visitor, class B=IVisitable<Visitor>>
struct Visitable {
  virtual void accept(Visitor const& v) const override {
    v.visit(static_cast<const I&>(*this));
  }
};

这仍然不能满足您的要求,因为您有一个值向量。而且多态值需要更多的工作。

将其作为X的唯一ptrs的向量,并添加struct A; struct B; struct X; struct X:Visitable<X, Visitor<A,B,X>> { }; struct A :Visitable<A, Visitor<A,B,X>, X> {}; struct B :Visitable<B, Visitor<A,B,X>, X> {}; 和一些virtual ~X(){}*,这将满足您的要求。

答案 1 :(得分:0)

您当前的解决方案存在一些问题:

  1. 您没有可以代表任何可访问类型的多态类型。这意味着您无法将所有AB值正确存储在集合中,从而无法访问集合中的每个元素。 X不能做到这一点,因为无法要求X的子类也可以继承Visitable类模板的实例。
  2. 您无法处理访问者/访问者类型的不匹配;您不能保证某个访问者类型可以访问集合中的所有值,而不能简单地使集合成为vector<A>vector<B>,在这种情况下,您将无法在访问者中存储不同可访问类型的值相同的集合。您要么需要一种在运行时处理 访客/访客不匹配情况的方法,要么需要更加复杂的模板结构。
  3. 不能将多态值直接存储在集合中。这是因为vector将其元素连续存储在内存中,因此必须为每个元素假定一定的恒定大小;就其性质而言,多态值的大小未知。解决方案是使用(智能)指针的集合来引用堆上其他位置的多态值。

这是对原始代码的有效改编:

#include <iostream>
#include <vector>
#include <memory>

template<typename T>
class Visitor;

class VisitorBase {
public:
    virtual ~VisitorBase() {}
};

class VisitableBase {
public:
    virtual void accept(VisitorBase& v) const = 0;
    virtual ~VisitableBase() {}
};

template <typename I>
class Visitable : public VisitableBase {
public:
    virtual void accept(VisitorBase& v) const {
        auto visitor = dynamic_cast<Visitor<I> *>(&v);
        if (visitor == nullptr) {
            // TODO: handle invalid visitor type here
        } else {
            visitor->visit(dynamic_cast<const I &>(*this));
        }
    }
};

template<typename T>
class Visitor : public virtual VisitorBase {
public:
    virtual void visit(const T& t) = 0;
};

struct A : public Visitable<A> {};
struct B : public Visitable<B> {};

class MyVisitor : public Visitor<A>, public Visitor<B> {
public:
    void visit(const A& a) override { std::cout << "Visiting A" << std::endl; }
    void visit(const B& b) override { std::cout << "Visiting B" << std::endl; }
};

int main() {
    MyVisitor v {};
    std::vector<std::shared_ptr<VisitableBase>> elems {
        std::dynamic_pointer_cast<VisitableBase>(std::make_shared<A>()),
        std::dynamic_pointer_cast<VisitableBase>(std::make_shared<B>())
    };
    for (const auto& x : elems) {
        x->accept(v);
    }
    A().accept(v);
    B().accept(v);
}