C ++, полиморфизм и итераторы - PullRequest
5 голосов
/ 22 ноября 2010

Я хочу иметь интерфейс Storage (абстрактный класс) и набор реализаций Storage (SQLite, MySQL, Memcached ...) для хранения объектов известного класса и извлечения подмножеств из Storage.
Мне понятенинтерфейс будет выглядеть следующим образом:

class Storable{int id; blah; blah; blah; string type;};
class Storage{
    virtual Storage::iterator get_subset_of_type(string type) = 0;
    virtual Storage::iterator end)_ = 0;
    virtual void add_storable(Storable storable) = 0;
};

И затем создайте реализации Storage, которые выполняют интерфейс.Теперь моя проблема заключается в следующем:

  • Итераторы не могут быть полиморфными, так как они возвращаются по значению.
  • Я не могу просто создать подкласс Storage :: iterator для моего заданного Storageреализация
  • Я думал о том, чтобы иметь итератор-обертку, который переносит и выполняет pimpl над полиморфным типом, который является подклассом реализаций Storage, но затем мне нужно использовать динамическую память и распределять ее повсеместно.

Есть подсказка?

Ответы [ 8 ]

3 голосов
/ 22 ноября 2010

Если вам нужен виртуальный интерфейс для итерации, что-то вроде этого?

#include <iostream>
#include <iterator>

struct Iterable {
    virtual int current() = 0;
    virtual void advance() = 0;
  protected:
    ~Iterable() {}
};

struct Iterator : std::iterator<std::input_iterator_tag,int> {
    struct Proxy {
        int value;
        Proxy(const Iterator &it) : value(*it) {}
        int operator*() { return value; }
    };
    Iterable *container;
    Iterator(Iterable *a) : container(a) {}
    int operator*() const { return container->current(); }
    Iterator &operator++() { container->advance(); return *this; }
    Proxy operator++(int) { Proxy cp(*this); ++*this; return cp; }
};

struct AbstractStorage : private Iterable {
    Iterator iterate() {
        return Iterator(this);
    }
    // presumably other virtual member functions...
    virtual ~AbstractStorage() {}
};

struct ConcreteStorage : AbstractStorage {
    int i;
    ConcreteStorage() : i(0) {}
    virtual int current() { return i; }
    virtual void advance() { i += 10; }
};

int main() {
    ConcreteStorage c;
    Iterator x = c.iterate();
    for (int i = 0; i < 10; ++i) {
        std::cout << *x++ << "\n";
    }
}

Это не полное решение - я не реализовал Iterator::operator== или Iterator::operator-> (последнее необходимо, если содержащийся тип является типом класса).

Я храню состояние в классе ConcreteStorage, что означает, что мы не можем иметь несколько итераторов в одном хранилище одновременно. Поэтому, вероятно, вместо того, чтобы Iterable являлся базовым классом хранилища, должна быть другая виртуальная функция хранилища для возврата нового Iterable. Тот факт, что это только входной итератор, означает, что все копии итератора могут указывать на один и тот же Iterable, поэтому им можно управлять с помощью shared_ptr (и либо Itertable должен иметь виртуальный деструктор, либо функцию newIterator должен вернуть shared_ptr или оба).

2 голосов
/ 22 ноября 2010

Я не вижу преимущества в том, что Storage является полиморфным.

В любом случае, обратите внимание, что итератор вообще не должен полиморфироваться.

Он просто должен использовать виртуальные методы изКласс хранения для его функциональности.Эти методы могут быть легко переопределены в потомках (создавая желаемую функциональность).

1 голос
/ 22 ноября 2010

Тот факт, что вы используете ядро ​​базы данных для хранения, не меняет того факта, что у вас здесь есть класс контейнера.

Короче говоря, вы почти наверняка должны использовать шаблон класса, который создается в зависимости от типа сохраняемого объекта. Изменение в механизмах хранения может быть обработано либо с помощью наследования, либо с помощью второго аргумента шаблона. Использование аргумента шаблона дает полиморфизм во время компиляции, в то время как наследование дает полиморфизм во время выполнения (т. Е. Вы можете менять механизмы хранения во время выполнения).

Возможно, вы захотите взглянуть на DTL , чтобы найти какое-то вдохновение (или вы можете избавить себя от многих проблем и просто использовать его до тех пор, пока не столкнетесь с проблемой).

1 голос
/ 22 ноября 2010

Я не совсем уверен, почему это проблема. Вам просто нужно реализовать все операторы итераторов (инкремент, разыменование и т. Д.), Чтобы они вызывали виртуальный метод объекта Storage.

0 голосов
/ 22 ноября 2010

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

Если вам действительно нужно разрешение во время выполнения, вам нужно добавить диспетчеризацию для вызова различных экземпляров вышеуказанной шаблонной функции.Виртуальная функция должна вызывать фактическую функцию шаблона алгоритма, но это будет переопределением виртуальной функции в некотором классе шаблона (т. Е. Виртуальная функция будет компилироваться отдельно для каждого экземпляра и вызывать разные экземпляры шаблонной функции алгоритма).Если вам нужна двойная / множественная отправка, пусть будет так.Жаль, что c ++ не поддерживает функцию, чтобы быть виртуальной по нескольким параметрам, вам придется использовать любую из распространенных идиом для двойной диспетчеризации.Но вызов функции фактического алгоритма должен выполняться после разрешения отправки.

0 голосов
/ 22 ноября 2010

Взгляните на adobe :: any_iterator или другую реализацию any_iterator (http://thbecker.net/free_software_utilities/type_erasure_for_cpp_iterators/any_iterator.html).. В ней реализована концепция полиморфного итератора, но вы все равно имеете дело с any_iterator по значению (возврат по значению, передача по значению и т. Д.).

0 голосов
/ 22 ноября 2010

Вы можете попробовать boost :: iterator, предусмотрено несколько адаптеров и фасадов.

http://www.boost.org/doc/libs/1_45_0/libs/iterator/doc/index.html

0 голосов
/ 22 ноября 2010

Кажется, что это случай динамического распределения в зависимости от возможности изменять размер состояния итератора (без перекомпиляции клиентов).

...