Design pattern "Container Visitor" without virtual methods?
I am developing the design of an application and I thought I might apply some sort of the Visitor design pattern, but it turned out that it's not exactly what I am looking for. Maybe someone can point me to the variant I require in this case?
Much of my code has a template argument "ContainerType" like
template <class ContainerType>
class MyClass
{
public:
void doSomething(ContainerType& container) { ... }
};
There is a currently small but growing number of "Containers" that usually share many data fields.
template<class ContainedType>
struct ContainerBase
{
ContainedType data;
};
struct Container1: ContainerBase<A>, ContainerBase<B>
{};
struct Container2: ContainerBase<A>, ContainerBase<C>
{};
Container1 and Container2 are now used as template arguments for MyClass (and others), where A,B,C are some defined classes. (I have some method to do something li开发者_如何学运维ke get<A>(container)
to access the contained data. This design provides compile-time safety that MyClass may be used with all container types that contain the required types.)
Now I would like to add the feature that "if the Container contains a certain type (e.g. A), then do something, otherwise do nothing".
This can be done with something that looks like the visitor (but note that no virtual methods are used). It even allows "if the Container contains A do this, if it contains D do something else, otherwise do nothing". This could be done with
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);
}
};
This is what I wanted, but I am looking for a better way to do it, because the implementation shown here requires implementing accept for every ContainerType. If someone derives from Container1
and ContainerBase<D>
but forgets to expand the accept methods, things will become bad. Even worse, I will require a const and non-const version of accept and some containers contain >5 types, so that won't look pretty either.
All container classes are build up by inheriting from ContainerBase<T>
multiple times, so I wondered if I could use this structure to implement the accept (and accept(..) const) in the ContainerBase class? I already looked at Lokis typelists but I don't know how to use them here. Do you have any idea?
Or is it possible to do this thing without the visitor-like structure?
Thanks a lot!
EDIT: I know I could go with RTTI but I'd like to avoid runtime checks and virtual methods if possible.
If you can change the way container classes are defined, it looks like it could be very easy to achieve with Boost.Fusion
For Example
#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 );
}
You could use boost::mpl to define a typelist of contained types as follows:
typedef boost::mpl::vector<A, B, C> ContainedTypes;
With boost::mpl::for_each you can call a functor for each of these contained types.
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;
}
then call
boost::mpl::for_each<ContainedTypes> (Visitor(MyClass<ContainerType>& item))
This will invoke the operator() of Visitor for each class in ContainedTypes. The downside of this approach with specialization is that you will need to specialize operator() for combinations of T and U.
Hope this helps,
Martin
The variant you require in this case might well be Boost.Variant :-)
精彩评论