关于c ++:设计模式“容器访问者”没有虚拟方法?

Design pattern “Container Visitor” without virtual methods?

我正在开发一个应用程序的设计,我想我可能会应用某种访问者设计模式,但结果发现这并不是我想要的。也许有人能给我指出这种情况下我需要的变体?

我的很多代码都有一个模板参数"containerType",比如

1
2
3
4
5
6
template <class ContainerType>
class MyClass
{
public:
  void doSomething(ContainerType& container) { ... }
};

目前,共享许多数据字段的"容器"数量很少,但在不断增加。

1
2
3
4
5
6
7
8
9
10
template<class ContainedType>
struct ContainerBase
{
  ContainedType data;
};

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

container1和container2现在用作myclass(和其他类)的模板参数,其中a、b、c是一些已定义的类。(我有一些方法可以做一些类似于get(container)的事情来访问包含的数据。这种设计提供了编译时安全性,myClass可以与包含所需类型的所有容器类型一起使用。)

现在我想添加这样一个特性:"如果容器包含某种类型(例如A),那么就做点什么,否则什么也不做"。

这可以通过看起来像访问者的东西来实现(但请注意,没有使用虚拟方法)。它甚至允许"如果容器包含do-this,如果它包含d,则执行其他操作,否则不执行任何操作"。这可以用

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
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
{
    template<class T>
    void accept(T& t)
    {
        t.visit(ContainerBase<A>::data);
        t.visit(ContainerBase::data);
    }
};

这是我想要的,但我正在寻找一种更好的方法来实现它,因为这里显示的实现需要为每个containerType实现accept。如果有人从Container1ContainerBase派生而忘记扩展接受方法,事情就会变得糟糕。更糟糕的是,我需要accept的const和non-const版本,一些容器包含5种以上的类型,所以看起来也不好看。

所有容器类都是通过多次从ContainerBase继承来构建的,所以我想知道是否可以使用这个结构来实现containerBase类中的accept(和accept(…)const)?我已经看了lokis的排字表,但我不知道如何在这里使用它们。你知道吗?

或者,在没有类似访客的结构的情况下,是否可以这样做?

谢谢!

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


如果您可以更改容器类的定义方式,那么看起来使用boost.fusion很容易实现。

例如

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
36
37
38
39
40
41
42
43
44
45
46
47
#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 );
}


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

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

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

例如

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

然后打电话

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

这将为containedtypes中的每个类调用visitor的operator()。这种专门化方法的缺点是,对于T和U的组合,需要专门化operator()。

希望这有帮助,

马丁


在这种情况下,您需要的变体很可能是boost.variant:-)