Какова самая чистая реализация этого контейнера типизированных контейнеров? - PullRequest
3 голосов
/ 05 июля 2010

У меня есть разные типы, скажем A, B, C, которые все наследуют от некоторого базового класса Base:

class Base { ... };
class A : public Base { ... };
class B : public Base { ... };
class C : public Base { ... };

Мне нужен контейнер, назовем его Master, который содержит указатели на объекты типов A, B и C. Я хочу, чтобы контейнер Master предоставлял итератор для всех содержащих Base объектов, а также специально типизированные итераторы для всех содержащих A, B и C объектов. В качестве бэкэнда хранилища я буду использовать std::vector, но было бы неплохо, если бы его потом можно было легко включить.

Концептуально, это интерфейс, который Master должен представить внешнему миру:

class Master {

  public:

    add(A *a);
    add(B *b);
    add(C *c);

    remove(Base *base);

    iterator<A*> a_begin();
    iterator<A*> a_end();
    iterator<B*> b_begin();
    iterator<B*> b_end();
    iterator<C*> c_begin();
    iterator<C*> c_end();
    iterator<Base*> base_begin();
    iterator<Base*> base_end();
    // also: reverse iterators, const iterators, reverse const iterators
};

Интерфейс не обязательно должен соответствовать этому точному синтаксису. Например, someMaster.begin<A>() тоже отлично подходит.

Проблема в том, что даже в этом упрощенном интерфейсе вы уже можете наблюдать некоторое дублирование кода. Это намного хуже в реализации. Это неприемлемо, потому что я хочу иметь возможность позже легко расширять контейнер Master, если я хочу добавить классы D, E и F (также наследуемые от Base). Желательно, чтобы я расширил его всего одной или двумя строками кода.

Все это может быть реализовано с большим количеством dynamic_cast ing, но это ужасно. Я думаю, что некоторая магия с шаблонами и множественным наследованием может помочь мне здесь. Что будет самой чистой реализацией этого класса?

Ответы [ 4 ]

4 голосов
/ 05 июля 2010

Вот набросок того, что я бы сделал:

// Beware, brain-compiled code ahead
template< typename T >
class typed_container
{
  typedef std::vector<T> data_t;
public:
  typedef data_t::iterator iterator;
  iterator begin() {return data_.begin();}
  iterator end  () {return data_.end();}
private:
  data_t data_;
};

typedef my_type_list<A,B,C> types_t;

class master : public derive_from< typed_container, types_t > {
  template< typename T >
  struct traits {
    typedef typename typed_container<T>::iterator iterator;
    iterator begin(typed_container<T>& c) {return c.begin();}
    iterator end  (typed_container<T>& c) {return c.end  ();}
  };
public:
  template< typename T > 
  typename traits<T>::iterator begin() {return traits<T>::begin(*this);}
  template< typename T > 
  typename traits<T>::iterator end  () {return traits<T>::end  (*this);}

  typedef my_assembling_iterator<types_t> iterator;

  iterator begin() {return my_assembling_iterator<types_t>.begin(*this);}
  iterator end  () {return my_assembling_iterator<types_t>.end  (*this);}
};

Это оставляет вам возможность реализовать my_type_list (довольно просто), derive_from (не так просто, но не слишком сложно), иmy_assembling_iterator (у меня еще не было необходимости делать что-то подобное).


Вы можете найти рабочую реализацию списка типов C ++ 03 здесь .Это займет всего девять шаблонных аргументов (но это легко расширяется), и вам придется написать

typedef my_type_list<A,B,C>::result_t types_t

, но это просто и бесплатно, и я знаю, что это работает (потому что я сам использую эту библиотеку).

Шаблон derive_from будет выглядеть примерно так:

//Beware, brain-compiled code ahead!
template< template<typename> class C, class  >
struct derive_from;

template< template<typename> class C >
struct derive_from< C, nil > {};

template< template<typename> class C, typename Head, typename Tail >
struct derive_from< C, my_type_list<Head,Tail> > : public C<Head>
                                                 , public derive_from<C,Tail> {};

Это оставляет итератор.Каковы ваши потребности в этом?Должен ли это быть итератор с произвольным доступом (жесткий) или достаточно прямого итератора?Вам нужен какой-то конкретный порядок для перебора элементов?

4 голосов
/ 05 июля 2010

Сначала вы должны взглянуть на Boost.Variant .
Позволяет хранить несколько несвязанных объектов в контейнере.
Сам итератор должен попытаться boost::get<T>(), чтобы его тип достиг следующего объекта типа T.
Если объект не относится к типу T, выполните итерацию вперед.
Если вы хотите перебрать все объекты, просто верните вариант этих объектов, чтобы A, B и C не обязательно были связаны с Base, если они вам не нужны, или просто указатель на Base, если все сценарии использования будут имеют общий базовый класс.

2 голосов
/ 05 июля 2010

Почему бы не RTTI + boost filter_iterator ? Люди обычно боятся RTTI, но filter_iterator с фильтром шаблонов, сравнивающим два type_infos, вполне подойдет.

0 голосов
/ 05 июля 2010

Я вижу здесь как минимум два вопроса:

Как избежать «избыточности» в интерфейсе?

Мастер-класс имеет некоторые функции, которые кажутся избыточными (a_begin / a_end против b_begin / b_end против c_begin / c_end). Мое первое предложение в этом случае - использовать итераторы в стиле Java, чтобы вы переместили функции end из интерфейса master в соответствующие итераторы.

Рассмотрим этот пример:

master *m = ..;
master::iterator<A*> a = m->getA():
while ( a->hasNext() ) {
    A *current = a->next();
    current->doSomething()
}

Таким образом, у вас есть только один итератор для каждого конкретного типа (A, B, C).

Я подозреваю, что, подумав, получатель итератора может быть шаблоном-членом, так что у вас есть только один шаблон get () и вы можете сказать m->get<A>(). По соображениям производительности, вы бы специализировали эти методы получения, чтобы просто перебирать определенные контейнеры (см. Ниже), я думаю.

Как сделать итераторы эффективными?

Итератор Base должен выполнять итерацию по всем содержащимся объектам, конкретные итераторы должны выполнять итерацию только по подмножествам содержащихся объектов. Для этого я предлагаю сделать master содержащим несколько std::vector объектов, по одному на конкретный тип.

Ваш итератор A может затем перебирать вектор, содержащий все A*, то же самое для других конкретных типов. Итератор Base будет просто перебирать все контейнеры, обрабатывая их как один непрерывный контейнер.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...