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

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

класс II интерфейс
{
    виртуальная пустота SomeMethod() = 0;
};

Объект класса
{
    IInterface* GetInterface() { ... }
};

Контейнер класса
{
частный:
    Элемент структуры
    {
        Object* pObject;
        [... другие члены...]
    };
    std:: list  m_items;
};

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

    MagicIterator  Begin();
    MagicIterator  End();

Для того, чтобы звонившие могли написать:

Контейнер c = [...] для (MagicIterator  i = c.Begin(); i! = C.End(); i++)
{
    IInterface* pItf = *i;
    [...]
} 

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

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

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

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

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

7 ответов

Решение

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

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

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

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

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

Сначала создайте итератор, который будет возвращать 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 может быть просто назначен на него.

Создать реферат 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's "var" here.  ;P

    return Iterator<Object *>(impl);
}

Звучит не слишком сложно. Вы можете определить итератор снаружи. Вы также можете использовать 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's 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) {
    // ...
}

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

Это действительно зависит от 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)
{
    // ...
}

Больше информации здесь.

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

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

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

Другие вопросы по тегам