关于c ++:虚拟模板函数访问者变通方法,包含模板派生类

Virtual template function visitor workaround with template derived classes

问题是,template virtual void foo()是非法的,我正试图使用访问者模式来解决这个问题(这通常是有效的)。然而,基的派生类是模板类,现在我正在访问类中遇到虚拟模板问题。如何解决这个问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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的值大约为100,并且不断引入新的D类。每个都将以同样的方式使用d。为了简单起见,假设每个访问函数将使用d

1
func<D>();

在哪里?

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

是基础中的某个助手函数。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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);
}


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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;
    }
};

演示

编辑:这里还有一种使用基本双调度的可能性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
        }
    }
};

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

这种蛮力的方法还有一些其他的缺点,这在亚历山德里斯科的书的第11章中有所收集。您还可以在这里阅读如何使用静态分派器来克服这些缺点。基本上,只需输入一次要考虑分派的类型,然后让代码创建上述逻辑。

演示