Адаптер итератора С++, который обертывает и скрывает внутренний итератор и преобразует итерированный тип

Поиграв с этим, я подозреваю, что это не возможно, но я думал, что попрошу экспертов. У меня есть следующий код на С++:

class IInterface
{
    virtual void SomeMethod() = 0;
};

class Object
{
    IInterface* GetInterface() { ... }
};

class Container
{
private:
    struct Item
    {
        Object* pObject;
        [... other members ...]
    };
    std::list<Item> m_items;
};

Я хочу добавить эти методы в контейнер:

    MagicIterator<IInterface*> Begin();
    MagicIterator<IInterface*> End();

Чтобы абоненты могли писать:

Container c = [...]
for (MagicIterator<IInterface*> i = c.Begin(); i != c.End(); i++)
{
    IInterface* pItf = *i;
    [...]
}

Таким образом, по существу, я хочу предоставить класс, который, по-видимому, выполняет итерацию по какой-либо коллекции (которую вызывающий объект Begin() и End() не может видеть) указателей IInterface, но который фактически выполняет итерацию по набору указатели на другие объекты (частные для класса Container), которые могут быть преобразованы в указатели IInterface.

Несколько ключевых моментов:

  • MagicIterator определяется вне Container.
  • Container::Item должен оставаться закрытым.
  • MagicIterator должен перебирать указатели IInterface, несмотря на то, что Container содержит a std::list<Container::Item>. Container::Item содержит Object*, а Object можно использовать для извлечения IInterface*.
  • MagicIterator должен быть повторно использован несколькими классами, которые напоминают Контейнер, но могут внутренне иметь разные реализации списков, содержащих разные объекты (std::vector<SomeOtherItem>, mylist<YetAnotherItem>) и IInterface*, полученные разными способами каждый раз.
  • MagicIterator не должен содержать код, специфичный для контейнера, хотя он может делегировать классы, которые делают, если такое делегирование не жестко закодировано для конкретных контейнеров внутри MagicIterator (так или иначе, как это разрешено автоматически компилятором, например).
  • Решение должно компилироваться в Visual С++ без использования других библиотек (например, boost), для чего требуется лицензионное соглашение от их авторов.
  • Кроме того, итерация может не выделять кучную память (поэтому no new() или malloc() на любом этапе), а не memcpy().

Спасибо за ваше время, даже если вы просто читаете; это действительно меня раздражало!

Обновление: Пока у меня были очень интересные ответы, ни один из них не удовлетворял всем вышеперечисленным требованиям. Примечательно, что сложными областями являются: i) развязка MagicIterator из Container каким-то образом (аргументы шаблона по умолчанию не разрезают его) и ii) избегают распределения кучи; но я действительно получаю решение, которое охватывает все вышеперечисленные пули.

Ответ 1

Теперь я нашел решение, которое подходит для моей первоначальной цели. Мне все равно это не нравится:)

Решение включает в себя MagicIterator, шаблон которого на IInterface * и создается как с void *, так и с итератором, размером байта упомянутого итератора и таблицей указателей на функции, которые выполняют стандартные функции итерации по указанному void *, такие как приращение, декремент, разыменование и т.д. MagicIterator предполагает, что безопасно memcpy данного итератора во внутренний буфер и реализует свои собственные члены, передавая свой собственный буфер как void * в поставляемые функции, как если бы это был исходный итератор.

Затем Container должен реализовать статические итерационные функции, которые отбрасывают предоставленный void * в std:: list:: iterator. Container:: begin() и Container:: end() просто создайте std:: list:: iterator, передайте указатель на него в MagicIterator вместе с таблицей его итерационных функций и верните MagicIterator.

Это несколько отвратительно и нарушает мое первоначальное правило относительно "no memcpy()" и делает предположения о внутренностях итераторов, о которых идет речь. Но он избегает выделения кучи, сохраняет внутреннюю часть Collection (включая Item), MagicIterator полностью независим от рассматриваемой коллекции и IInterface *, а в теории позволяет MagicIterators работать с любой коллекцией (при условии, что ее итераторы могут быть безопасно memcopy() г).

Ответ 2

Я думаю, у вас есть два отдельных вопроса:

Сначала создайте итератор, который вернет IInterface* из вашего list<Container::Item>. Это легко сделать с помощью boost::iterator_adaptor:

class cont_iter
  : public boost::iterator_adaptor<
        cont_iter                       // Derived
      , std::list<Container::Item>::iterator // Base
      , IInterface*                     // Value
      , boost::forward_traversal_tag    // CategoryOrTraversal
      , IInterface*                     // Reference :)
    >
{
 public:
    cont_iter()
      : cont_iter::iterator_adaptor_() {}

    explicit cont_iter(const cont_iter::iterator_adaptor_::base_type& p)
      : cont_iter::iterator_adaptor_(p) {}

 private:
    friend class boost::iterator_core_access;
    IInterface* dereference() { return this->base()->pObject->GetInterface(); }
};

Вы должны создать этот тип как внутренний в Container и вернуться из его методов begin() и end().

Во-вторых, вы хотите, чтобы время выполнения - полиморфное MagicIterator. Это как раз то, что делает any_iterator. MagicIterator<IInterface*> - это просто any_iterator<IInterface*, boost::forward_traversal_tag, IInterface*>, а cont_iter может быть просто назначено ему.

Ответ 3

Создайте абстрактный класс IteratorImplementation:

template<typename T>
class IteratorImplementation
{
    public:
        virtual ~IteratorImplementation() = 0;

        virtual T &operator*() = 0;
        virtual const T &operator*() const = 0;

        virtual Iterator<T> &operator++() = 0;
        virtual Iterator<T> &operator--() = 0;
};

И класс Iterator, чтобы обернуть его:

template<typename T>
class Iterator
{
    public:
        Iterator(IteratorImplementation<T> * = 0);
        ~Iterator();

        T &operator*();
        const T &operator*() const;

        Iterator<T> &operator++();
        Iterator<T> &operator--();

    private:
        IteratorImplementation<T> *i;
}

Iterator::Iterator(IteratorImplementation<T> *impl) :
    i(impl)
{
}

Iterator::~Iterator()
{
    delete i;
}

T &Iterator::operator*()
{
    if(!impl)
    {
        // Throw exception if you please.
        return;
    }

    return (*impl)();
}

// etc.

(Вы можете сделать IteratorImplementation класс "внутри" Iterator, чтобы все было в порядке.)

В вашем классе Container верните экземпляр Iterator с пользовательским подклассом IteratorImplementation в ctor:

class ObjectContainer
{
    public:
        void insert(Object *o);
        // ...

        Iterator<Object *> begin();
        Iterator<Object *> end();

    private:
        class CustomIteratorImplementation :
            public IteratorImplementation<Object *>
        {
            public:
                // Re-implement stuff here.
        }
};

Iterator<Object *> ObjectContainer::begin()
{
    CustomIteratorImplementation *impl = new CustomIteratorImplementation();  // Wish we had C++0x "var" here.  ;P

    return Iterator<Object *>(impl);
}

Ответ 4

Звучит слишком сложно. Вы можете определить итератор снаружи. Вы также можете использовать typedefs. Мне кажется, что-то вроде этого. Обратите внимание, что это было бы более чистым, если бы MagicIterator не был бы свободным шаблоном, но, возможно, был членом Item, который был бы указан в контейнере. Как и сейчас, в нем есть циклическая ссылка, из-за которой не нужно писать какой-то уродливый код обхода.

namespace detail {
    template<typename T, typename U>
    struct constify;

    template<typename T, typename U>
    struct constify<T*, U*> {
        typedef T * type;
    };

    template<typename T, typename U>
    struct constify<T*, U const*> {
        typedef T const * type;
    };
}

template<typename DstType, 
         typename Container,
         typename InputIterator>
struct MagicIterator;

class Container
{
private:
    struct Item
    {
        Object* pObject;
    };

    std::list<Item> m_items;

public:

    // required by every Container for the iterator
    typedef std::list<Item> iterator;
    typedef std::list<Item> const_iterator;

    // convenience declarations
    typedef MagicIterator< IInterface*, Container, iterator > 
        item_iterator;
    typedef MagicIterator< IInterface*, Container, const_iterator > 
        const_item_iterator;

    item_iterator Begin();
    item_iterator End();
};

template<typename DstType, 
         typename Container = Container,
         typename InputIterator = typename Container::iterator>
struct MagicIterator : 
    // pick either const T or T, depending on whether it a const_iterator.
    std::iterator<std::input_iterator_tag, 
                  typename detail::constify<
                           DstType, 
                           typename InputIterator::value_type*>::type> {
    typedef std::iterator<std::input_iterator_tag, 
                 typename detail::constify<
                          DstType, 
                          typename InputIterator::value_type*>::type> base;
    MagicIterator():wrapped() { }
    explicit MagicIterator(InputIterator const& it):wrapped(it) { }
    MagicIterator(MagicIterator const& that):wrapped(that.wrapped) { }

    typename base::value_type operator*() {
        return (*wrapped).pObject->GetInterface();
    }

    MagicIterator& operator++() {
        ++wrapped;
        return *this;
    }

    MagicIterator operator++(int) {
        MagicIterator it(*this);
        wrapped++;
        return it;
    }

    bool operator==(MagicIterator const& it) const {
        return it.wrapped == wrapped;
    }

    bool operator!=(MagicIterator const& it) const {
        return !(*this == it);
    }

    InputIterator wrapped;
};

// now that the iterator adepter is defined, we can define Begin and End
inline Container::item_iterator Container::Begin() {
    return item_iterator(m_items.begin());
}

inline Container::item_iterator Container::End() {
    return item_iterator(m_items.end());
}

Теперь начните использовать его:

for(MagicIterator<IInterface*> it = c.Begin(); it != c.End(); ++it) {
    // ...
}

Вы также можете использовать итератор mixin, предоставляемый boost, который работает как входная версия boost:: function_output_iterator. Он называет ваш итератор operator(), который затем возвращает соответствующее значение, делая то, что мы делаем выше в нашем operator* в принципе. Вы найдете его в random/detail/iterator_mixin.hpp. Вероятно, это приведет к уменьшению количества кода. Но это также требует взломать нашу шею, чтобы обеспечить дружеский материал, потому что Item является частным, и итератор не определен внутри предмета. Во всяком случае, удачи:)

Ответ 5

Это действительно зависит от Container, потому что возвращаемые значения c.Begin() и c.End() определяются реализацией.

Если список возможных Container известен MagicIterator, можно использовать класс-оболочку.

template<typename T>
class MagicIterator
{
    public:
        MagicIterator(std::vector<T>::const_iterator i)
        {
            vector_const_iterator = i;
        }

        // Reimplement similarly for more types.
        MagicIterator(std::vector<T>::iterator i);
        MagicIterator(std::list<T>::const_iterator i);
        MagicIterator(std::list<T>::iterator i);

        // Reimplement operators here...

    private:
        std::vector<T>::const_iterator vector_const_iterator;
        std::vector<T>::iterator       vector_iterator;
        std::list<T>::const_iterator   list_const_iterator;
        std::list<T>::iterator         list_iterator;
};

простой способ заключается в использовании шаблона, который принимает тип Container:

// C++0x
template<typename T>
class Iterator :
    public T::iterator
{
    using T::iterator::iterator;
};

for(Iterator<Container> i = c.begin(); i != c.end(); ++i)
{
    // ...
}

Подробнее здесь

Ответ 6

Я не вижу причин, по которым вы не можете реализовать это точно так же, как вы его изложили... Я что-то упустил?

Чтобы уточнить, вам нужно добавить какие-либо методы доступа в свой класс Container. Они могут быть частными, и вы можете объявить MagicIterator своим другом, если вы считаете, что это лучший способ его инкапсулировать, но я бы разоблачил их напрямую. Эти методы доступа будут использовать обычный STL-итератор внутри контейнера и выполнить преобразование на IInterface. Таким образом, итерация будет выполняться с помощью методов доступа к контейнерам, а MagicIterator будет просто своего рода прокси-объектом, чтобы упростить его. Чтобы сделать его реентерабельным, вы могли бы передать MagicIterator в какой-то идентификатор, чтобы посмотреть итератор STL внутри контейнера, или вы могли бы фактически передать его в STL-итераторе как void *.

Ответ 7

Посетитель может быть более простым (и, следовательно, более простым в обслуживании) решением.