设计模式“容器访客”没有虚拟方法?

时间:2010-10-09 07:45:57

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

我正在开发一个应用程序的设计,我想我可能会应用某种Visitor design pattern,但事实证明它并不是我想要的。也许有人可以指出我在这种情况下需要的变体?

我的大部分代码都有一个模板参数“ContainerType”,如

template <class ContainerType>
class MyClass
{
public:
  void doSomething(ContainerType& container) { ... }
};

目前有一小部分但数量不断增加的“容器”通常共享许多数据字段。

template<class ContainedType>
struct ContainerBase 
{ 
  ContainedType data;
};

struct Container1: ContainerBase<A>, ContainerBase<B>
{};
struct Container2: ContainerBase<A>, ContainerBase<C>
{};

Container1和Container2现在用作MyClass(和其他)的模板参数,其中A,B,C是一些已定义的类。 (我有一些方法可以使用get<A>(container)来访问包含的数据。这种设计提供了编译时的安全性,MyClass可以用于包含所需类型的所有容器类型。)

现在我想添加“如果容器包含某种类型(例如A),然后做某事,否则什么也不做”的功能。

这可以通过看起来像访问者的东西来完成(但请注意,没有使用虚拟方法)。它甚至允许“如果容器包含A执行此操作,如果它包含D执行其他操作,则不执行任何操作”。这可以通过

来完成
template <class ContainerType>
class MyClass
{
public:
    void doSomething(ContainerType& container) 
    { 
        container.accept(*this); 
    }

    void visit(B& b){...}
    void visit(D& d){...}

    template<typename T>
    void visit(T& t){}
};


struct Container1: ContainerBase<A>, ContainerBase<B>
{
    template<class T>
    void accept(T& t)
    {
        t.visit(ContainerBase<A>::data);
        t.visit(ContainerBase<B>::data);
    }
};

这就是我想要的,但我正在寻找一种更好的方法,因为这里显示的实现需要为每个ContainerType实现accept。如果有人来自Container1ContainerBase<D>但忘记扩展接受方法,那么事情就会变得糟糕。更糟糕的是,我需要一个const和非const版本的accept,而一些容器包含&gt; 5种类型,所以看起来也不一样。

所有容器类都是通过多次从ContainerBase<T>继承来构建的,所以我想知道我是否可以使用这个结构在ContainerBase类中实现accept(和accept(..)const)?我已经看过Lokis类型列表,但我不知道如何在这里使用它们。你有什么想法吗?

或者没有类似访客的结构可以做这件事吗?

非常感谢!

编辑:我知道我可以使用RTTI,但如果可能的话,我想避免运行时检查和虚拟方法。

3 个答案:

答案 0 :(得分:3)

如果你可以改变容器类的定义方式,看起来很容易用Boost.Fusion实现

例如

#include <iostream>

#include <boost/fusion/container/vector.hpp>
#include <boost/fusion/algorithm/iteration/for_each.hpp>

struct A {};
struct B {};
struct C {};

namespace fusion = boost::fusion;

struct Container1 : fusion::vector< A, B > {};

struct Container2 : fusion::vector< A, C > {};

struct doSomethingWithData {
    void operator()( B &b ) const
    {
        std::cout << "do something with B" << std::endl;
    }

    void operator()( C &c ) const
    {
        std::cout << "do something with C" << std::endl;
    }

    template < typename T >
    void operator()( T &t ) const
    {
        std::cout << "Neither B nor C" << std::endl;
    }
};

template < typename ContainerType >
void doSomething( ContainerType &container )
{
    fusion::for_each( container, doSomethingWithData() );
}

int main()
{
    Container1 c1;
    doSomething( c1 );
    std::cout << "----------------------" << std::endl;
    Container2 c2;
    doSomething( c2 );
}

答案 1 :(得分:1)

您可以使用boost :: mpl来定义包含类型的类型列表,如下所示:

typedef boost::mpl::vector<A, B, C> ContainedTypes;

使用boost :: mpl :: for_each,您可以为每个包含的类型调用一个仿函数。

e.g。

template<class U>
class Visitor
{
public:
  Visitor(MyClass<U>& item) : _item(item)
{}

template<class T>
void operator() (T)
{
  // Do what ever you want, this may be specialized as needed
}

private:
  MyClass<U>& item;
}

然后致电

boost::mpl::for_each<ContainedTypes> (Visitor(MyClass<ContainerType>& item))

这将为ContainedTypes中的每个类调用Visitor的operator()。 这种专业化方法的缺点是你需要为T和U的组合专门化operator()。

希望这有帮助,

马丁

答案 2 :(得分:0)

在这种情况下您需要的变体可能是Boost.Variant: - )