Дизайн шаблона «Контейнер посетитель» без виртуальных методов? - PullRequest
1 голос
/ 09 октября 2010

Я разрабатываю дизайн приложения, и я подумал, что мог бы применить какой-то шаблон Visitor design , но оказалось, что это не совсем то, что я ищу.Может быть, кто-то может указать мне вариант, который мне нужен в этом случае?

Большая часть моего кода имеет аргумент шаблона "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);
    }
};

Это то, что я хотел, но я ищу лучший способ сделать это, потому что реализация, показанная здесь, требует реализации accept для каждого ContainerType.Если кто-то происходит от Container1 и ContainerBase<D>, но забывает расширять методы приема, все станет плохо.Хуже того, мне потребуется константная и неконстантная версия accept, а некоторые контейнеры содержат> 5 типов, поэтому это тоже не будет выглядеть красиво.

Все классы контейнеров создаются путем наследования от ContainerBase<T>Несколько раз, поэтому я задавался вопросом, могу ли я использовать эту структуру для реализации accept (и accept (..) const) в классе ContainerBase?Я уже посмотрел на списки типов Lokis, но я не знаю, как их использовать здесь.У вас есть какие-либо идеи?

Или это возможно сделать без структуры, похожей на посетителя?

Большое спасибо!

РЕДАКТИРОВАТЬ: Я знаю, что я мог бы пойтис RTTI, но я бы хотел по возможности избегать проверок во время выполнения и виртуальных методов.

Ответы [ 3 ]

3 голосов
/ 09 октября 2010

Если вы можете изменить способ определения классов контейнеров, похоже, что это будет очень легко сделать с помощью 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 голос
/ 09 октября 2010

Вы можете использовать boost :: mpl для определения списка типов содержащихся типов следующим образом:

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

С boost :: mpl :: for_each вы можете вызывать функтор для каждого из этих содержащихся типов.

, например

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))

Это вызовет оператор () Visitor для каждого класса в ContainedTypes. Недостатком этого подхода со специализацией является то, что вам нужно будет специализировать operator () для комбинаций T и U.

Надеюсь, это поможет,

Martin

0 голосов
/ 09 октября 2010

В данном случае вам может потребоваться вариант Boost.Variant : -)

...