【问题标题】:Virtual template function visitor workaround with template derived classes使用模板派生类的虚拟模板函数访问者解决方法
【发布时间】:2014-11-24 03:55:47
【问题描述】:

问题是template <typename T> virtual void foo() 是非法的,我正在尝试使用访问者模式来解决这个问题(这通常是已知的)。但是 Base 的派生类是模板类,现在我在访问者类中遇到了虚拟模板问题。如何解决这个问题?

struct Base {
//  template <typename T> virtual void foo() = 0;  // illegal
    virtual void foo (class Visitor& visitor) = 0;  // The attempted solution
};

template <typename D>
struct Derived : Base {
    virtual void foo (Visitor&) override;
};

struct Visitor {
    //template <typename D> // same problem again!
    virtual void visit (Derived<D>*) const = 0;
};

template <typename T, typename D>
struct FooVisitor : Visitor {
    virtual void visit (Derived<D>*) const override {/*Do whatever with T*/}
};

template <typename D>
void Derived<D>::foo (Visitor& visitor) {visitor.visit(this);}

对于所有解决方案,我们假设 D 应该有大约一百个值,并且不断引入新的 D 类。每个人都会以同样的方式使用 D。为简单起见,我们假设每个访问函数都使用 D 和

func<D>();

在哪里

template <typename D> void Base::func();

是 Base 中的一些辅助函数。

【问题讨论】:

  • 你想在你的模板中做什么 virtual void foo(); ?
  • 这是一个普遍的问题,不是吗?解决方案应该独立于 T 的意图。也许对于 T 的某些目标可能有一个特殊的解决方案,但需要有一个始终有效的通用解决方案。
  • 如何将模板 foo() 设为非虚拟,然后从 foo() 调用虚拟 fooImpl()?
  • 你需要在Derived&lt;D&gt;::foo()做一个downcast。
  • 您的Visitor 应该为您所期望的每个D 提供virtual void visit (Derived&lt;D&gt;*) const = 0;

标签: c++ templates virtual


【解决方案1】:

这是一个可行的解决方案。请注意,这里的假设是您只使用正确的类型进行调用:

struct Base {
    virtual void foo(struct Visitor& visitor) = 0;
};

template <typename D>
struct Derived : Base {
    virtual void foo (Visitor&v) override;
};

struct Visitor {
    virtual ~Visitor() {} // Make this class polymorphic.
};

template <typename D>
struct Visitor_tmpl : public Visitor {
    virtual void visit (Derived<D>*) const {/*Do whatever with T*/}
};

template <typename T, typename D>
struct FooVisitor : Visitor_tmpl<D> {
    virtual void visit (Derived<D>*) const override {/*Do whatever with T*/}
};

template <typename D>
void Derived<D>::foo(Visitor&v) {
    // In this function, D has been bound now to a specific type, so we downcast.
    // It will throw an exception if not the right type.
    dynamic_cast<Visitor_tmpl<D> &>(v).visit(this);
}

int main() {
    Derived<int> d;
    FooVisitor<double, int> v;
    d.foo(v);
}

【讨论】:

  • 是的,这行得通,但是用户必须为它访问的每个类定义一个访问者,这很麻烦。另外,当使用FooVisitor&lt;double, float&gt; v; 进行访问时,程序会在运行时崩溃。
  • 对于所有解决方案,我们假设 D 应该有大约一百个值,并且不断引入新的 D 类。每个人都会以同样的方式使用 T。
  • @davidhigh:不需要添加任何类。当然,您需要定义正确类型的对象,但无论如何您都需要这样做。
  • @prestokeys:此解决方案不需要为新 D 添加任何新类。它确实需要定义访问者,但无论如何你总是需要它。
  • @davidhigh:它本身不会崩溃,如果使用了错误的类型,它会抛出异常。 OP 必须提供更多关于在这种情况下应该发生什么的信息。
【解决方案2】:

Jarod42 提到了一种可能的解决方案,即指定所有可能出现的类型。但是,通常您希望提供一个采用 Base* 的标准实现,并且仅在需要时重载它。

struct Type1 {};
//...
struct TypeN {};

struct Visitor
{
    virtual ~Visitor() {}

    virtual void visit (Base*) const = 0;
    virtual void visit (Derived<Type1>* d) const { visit(static_cast<Base*>(d)); };
    //...
    virtual void visit (Derived<TypeN>* d) const { visit(static_cast<Base*>(d)); };
};


struct FooVisitor : public Visitor
{
    virtual void visit (Base* base) const override
    {
        std::cout<<"visiting base class."<<std::endl;
    }

    //further definitions for those types that require a special implementation
    virtual void visit (Derived<TypeN>* d) const override
    {
        std::cout<<"visiting class of type Derived<TypeN>."<<std::endl;
    }
};

DEMO


编辑:这是使用基本double dispatch 的另一种可能性:

struct Visitor
{
    virtual ~Visitor() {}

    virtual void visit (Base*) const = 0;
};

struct FooVisitor : public Visitor
{
    virtual void visit (Base* base) const override
    {
        if(Derived<TypeN>* d = dynamic_cast<Derived<TypeN>*>(base))
        {
            std::cout<<"visiting class of type Derived<TypeN>."<<std::endl;
        }
        else
        {
            std::cout<<"visiting base class."<<std::endl;
        }
    }
};

它使您不必在基类中声明每个可能的变量类型,但可能不如以前的解决方案效率高。

这种蛮力方法还有一些其他的缺点,收集到in chapter 11 of Alexandrescu's book。您还可以阅读如何通过使用静态调度程序来克服这些缺点。基本上,您只需输入一次要考虑调度的类型,然后让代码创建上述逻辑。

DEMO

【讨论】:

  • FooVisitor 本身有一个模板 T,可以与它的访问函数一起使用,但它并没有改变第一个解决方案的想法。然而,(第一个)解决方案很难管理这么多派生类(并且不断添加新类)。另外还有所有重复的代码,因为否则可以以相同的方式使用 Type1, Type2, ... TypeN 轻松总结实现(但这些不能在此处表示为 D)。至于第二种方案,要检查的if语句会很多很多,对吧?
  • @prestokeys:我同意,我现在也很难管理。我避免使用您的模板参数T,因为您的代码没有使用它,而且我不了解它的用法。也许你可以澄清一下?重复的代码,我猜,可以使用一个或多个采用Base* 或函数模板的默认实现来处理?
  • 是的,一个辅助函数 helper() 可以在所有访问函数中从 Base 实现,但是每个访问函数仍然需要调用它们,例如{helper&lt;Type1,T&gt;();}, {helper&lt;Type2,T&gt;();}, ... {helper&lt;TypeN,T&gt;();} 在访问功能中。但至少这是一个进步。当然,{helper&lt;D,T&gt;();} 是理想的,但不可能。不使用访问者模式是否可以进行多次调度?我的访客解决方案尝试只是我的尝试。
  • @davidhigh:也应该可以自动生成调度代码,但我们可能需要来自 OP 的更多信息来了解最佳方式。例如,Derived&lt;D&gt; 的每个实例化都可能有一个静态成员,其 ctor 向映射添加了一个条目。当然,那么OP需要注意初始化的顺序,但是可以做到。
  • 为简单起见,我们假设每个访问函数都会使用 T 和 func&lt;T&gt;();,其中 template &lt;typename T&gt; void Base::func(); 是 Base 中的一些辅助函数。如果可以使用 D,则改为func&lt;T,D&gt;();
猜你喜欢
  • 2011-02-22
  • 2010-12-06
  • 1970-01-01
  • 2013-10-07
  • 1970-01-01
  • 2012-10-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多