СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H Универсальный итератор в C++ (Или я хочу странного?) в черновиках

C++

Приветствую уважаемое сообщество. Хочу обсудить одну проблему с итераторами в c++.
Не возможно сделать виртуальный метод принимающий итераторы любой STL( или STL-подобной) коллекции. Приведу пример:
Допустим надо создать абстрактное хранилище для экземпляров MyClass, а затем реализовать его для базы данных и для сохранения в файл. Для простоты примера описываю один метод Update.


class MyClass { ... };

class AbstarctRepositoryOfMyClass {
public:
    virtual void Update(Iterator<MyClass> begin, Iterator<MyClass> end) = 0;
};

class DbRepositoryOfMyClass : public AbstarctRepositoryOfMyClass {
public:
    void Update(Iterator<MyClass> begin, Iterator<MyClass> end) { ... }
};

class JsonFileRepositoryOfMyClass : public AbstarctRepositoryOfMyClass {
public:
    void Update(Iterator<MyClass> begin, Iterator<MyClass> end) { ... }
};

Здесь DbRepositoryOfMyClass и JsonFileRepositoryOfMyClass реализуют каждый свою стратегию хранения. Используем их через AbstarctRepositoryOfMyClass, выбрав в момент инициализации. Метод Update принимает любую коллекцию, будь то std::vector, std::list, или любая другая, у которой можно перебрать элементы. Красота.
Но такой код не реализуем. По крайней мере, нет простого решения. Какой класс подойдёт на роль Iterator<MyClass>? std::vector<Myclass>::const_iterator или std::list<MyClass>::const_iterator? Хорошие варианты. Но они не имеют общего предка. При выборе одного остальные коллекции исключаются. А так хочется не накладывать лишних ограничений.
Но постойте. algorithm реализует множество функций принимающих любые коллекции. Как в них реализованы типы итераторов? Типы итераторов передаются в качестве аргументов шаблона. Для нашего случая это не подходит. Ведь шаблоны не могут быть виртуальными методами.

Напрашивается решение: Универсальный итератор-обёртка над итераторами из STL.
Скажите, вам тоже нужен универсаль итератор или вы знаете решение получше?

–4
933

комментарии (5)

+1
reversecode ,  
а причем здесь хабр? вам на тостер
+2
OldFisher ,  
Если вкратце, то работа с итератором выносится в шаблонный не-виртуальный метод update класса AbstractRepository. Тот в свою очередь обращается к виртуальному приватному методу (допустим, updateObject), работающему с отдельными объектами, который наследники реализуют по-своему. Можно рассчитывать, что update будет заинлайнен в месте вызова.
0
sergegers ,  
Здесь есть два подхода.
Первый, отказаться от наследования, виртуальных функций и использовать шаблоны. Тогда ваш код следует переписать в виде:

template <typename Iterator>
class RepositoryOfMyClass
{
public:
    void Update(Iterator<MyClass> begin, Iterator<MyClass> end) 
   {
       // Do something
   }
};

using DbRepositoryOfMyClass = RepositoryOfMyClass<DbIterator>;

// итд...


Минусы такого подхода — код весь в хидерах, плюсы — максимально быстр.

Второй подход — так называемое(-ый) type erasure. Надо использовать type erasure итератор, например boost.any_iterator, к которому можно кастить соответствующие по типу итераторы std::vector::iterator, std::list::iterator итд.
Минусы такого подхода — потеря перформанса, any iterator реализован через виртуальные функции.
+1
hdfan2 ,  
Необязательно делать шаблонным весь класс, достаточно шаблонного метода. Тогда в хидере нужно будет написать только его код.
0
tzlom ,   * (был изменён)

Как бы я поступил на вашем месте:
Нужно решить, вам нужны ВСЕ виды итераторов по контейнерам, или только некоторые из большого списка. Для ВСЕХ есть уже выше подсказали boost::any_iterator, или паттерн проектирования Visitor.
Для большого но ограниченного количества шаблон прячется в реализацию, как то так:


// .h
class DbRepositoryOfMyClass : public AbstarctRepositoryOfMyClass {
public:
    void Update(std::vector<MyClass>::iterator begin, std::vector<MyClass>::iterator end);
    void Update(std::list<MyClass>::iterator begin, std::list<MyClass>::iterator end);
    // и так далее, при желании выносится в #define
private:
    template<Iterator>
    void update_priv(Iterator begin, Iterator end);
};
// .cpp

DbRepositoryOfMyClass::Update(std::vector<MyClass>::iterator begin, std::vector<MyClass>::iterator end)
{
    update_priv(begin, end);
}

DbRepositoryOfMyClass::Update(std::vector<MyClass>::iterator begin, std::vector<MyClass>::iterator end)
{
    update_priv(begin, end);
}

Если хотите спрятать реализацию update_priv то переносим его в дружественный класс и передаём ему this.