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

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

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

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

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

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

Johannes Schaub - litb 23.01.2009 00:31

Указатель должен быть получен путем вызова Object :: GetInterface (), поэтому на него нельзя полагаться как на член класса.

El Zorko 23.01.2009 00:50

MagicIterator не может получить доступ к внутренним компонентам контейнера или не должен?

MSN 23.01.2009 01:26

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

El Zorko 23.01.2009 01:37
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
15
4
12 865
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

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

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

Я не хочу делать Container :: Item общедоступным. Я хочу, чтобы итератор не имел внешнего отношения к std :: list :: iterator; чтобы казалось, что он выполняет итерацию по набору указателей IInterface *, а Item остается полностью закрытым для контейнера. IInterface * происходит от Item.pObject-> GetInterface ().

El Zorko 23.01.2009 01:01

мое решение не требует, чтобы вы делали элемент общедоступным. Итератор внешне не связан. Аксессоры обертывают std :: list :: iterator, поэтому только внутренняя часть контейнера знает, что это реализация. Вот почему я предложил использовать void *, чтобы передать его, чтобы сделать его реентерабельным.

rmeador 23.01.2009 01:06

Извините, это была опечатка. [Это, как вы, наверное, заметили, я здесь впервые. Очень жаль!] Я действительно хочу, чтобы MagicIterator был отделен от контейнера; Я хотел бы иметь возможность повторно использовать его в других произвольных классах, не меняя его, даже если, скажем, у других должен быть клей, чтобы он работал.

El Zorko 23.01.2009 01:09

ну, вы могли бы заставить Контейнер реализовывать некоторый интерфейс, который предоставляет соответствующие методы доступа и, таким образом, иметь возможность повторно использовать MagicIterator с чем-либо еще, что реализует этот интерфейс, но кроме этого, у меня нет идей ... извините.

rmeador 23.01.2009 01:33

Некоторая форма IMagicIteratable <IInterface *> ... это мысль. Но как написать методы доступа в контейнере таким образом, чтобы использовать выделенный в стеке итератор std :: list <Item>, не подвергая его (и частный класс Item) вызывающей стороне указанных методов?

El Zorko 23.01.2009 01:48

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

rmeador 23.01.2009 02:09

Рискуя показаться глупым, если MagicIterator обращается к реальному итератору через нетипизированный указатель, где этот итератор? Я действительно хочу, чтобы это было где-то в стеке. Этого не может быть, например. список в контейнере, конечно, так как я не хочу выделения кучи во время итерации ... и я точно так же не хочу его new ().

El Zorko 23.01.2009 02:28

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

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

Он не пытается поддерживать несколько типов контейнеров одним и тем же итератором (ala any_iterator) ...

Greg Rogers 23.01.2009 01:34

Да, основная задача - скрыть тот факт, что мы действительно используем std :: vector :: iterator <Item>, и сделать так, чтобы он выглядел так, как будто мы перебираем указатели IInterface * (полученные из объекта в каждом элементе) . В частности, Item должен оставаться закрытым для контейнера, в отличие от Object и IInterface.

El Zorko 23.01.2009 01:42

Создайте абстрактный класс 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);
}

Я пробовал это, но, к сожалению, абстрактный класс Iterator не может быть создан, поэтому вызывающий не может хранить его где-либо (даже в заголовке цикла for), не зная конкретного типа.

El Zorko 23.01.2009 01:11

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

strager 23.01.2009 01:24

for (Iterator <Object *> i = c.begin (); i! = c.end (); i ++) ... не будет компилироваться, поскольку Iterator <> является абстрактным.

El Zorko 23.01.2009 01:33

Ах, ты прав. Блин, и я думал, что у меня что-то работает. =] У вас мог есть оболочка вокруг абстрактного класса, и вы передаете обернутый класс конструктору объекта Iterator. Я думаю, это сработает.

strager 23.01.2009 02:12

Собственно, если присмотреться, этого не пойдет; причина, по которой я этого не сделал, заключается в том, что в моем случае у меня не может быть распределения кучи, вызванного итерацией, поэтому "новый" отсутствует. alloca () (возможно, через зловеще перегруженный оператор new) тоже не поможет, так как он выйдет за пределы области видимости, когда ObjectContainer :: begin () вернется.

El Zorko 24.01.2009 17:38

Звучит не слишком сложно. Вы можете определить итератор снаружи. Вы также можете использовать 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. В любом случае, удачи :)

Это компилируется и работает прекрасно, за исключением того, что Item является частным для контейнера. Я мог бы переместить MagicIterator в контейнер, но я действительно хочу, чтобы MagicIterator был применим к нескольким классам, подобным контейнеру, которые, возможно, реализуют специфичную для MagicIterator «клеящую» функциональность.

El Zorko 23.01.2009 02:06

@El Zorko, вот для чего друзья.

strager 23.01.2009 02:08

Эль, Зорько. итератор - друг Container. поэтому он должен видеть элемент, даже если он частный

Johannes Schaub - litb 23.01.2009 02:09

Эль, Зорько. если вы хотите отделить итератор от контейнера, сделайте контейнер также параметром шаблона и подтвердите, что «каждый контейнер должен иметь typedef, который объявляет InputIterator, обернутый как« wrapped_iterator », например. Затем вы можете выполнить Container :: Item, чтобы получить тип элемента тоже

Johannes Schaub - litb 23.01.2009 02:12

(и по умолчанию для параметра InputIterator значение Container :: wrapped_iterator или полностью удалите его)

Johannes Schaub - litb 23.01.2009 02:13

Эй, Зорько. О, это интересно. Комо также отвергает это. аргументы по умолчанию не кажутся благословенными дружбой. мне придется кое-что поменять :)

Johannes Schaub - litb 23.01.2009 02:31

При ближайшем рассмотрении, это, к сожалению, тоже не совсем соответствует требованиям. MagicIterator должен принимать аргумент шаблона, указывающий его контейнер и реальный итератор, который напрямую связывает его с классом COntainer. Мне нужен развязанный MagicIterator для работы с несколькими классами в стиле контейнера.

El Zorko 24.01.2009 17:47

Е.И. Зорько, это невозможно без новых. тогда ваш итератор должен быть полиморфным. но ты сказал, что не хочешь этого.

Johannes Schaub - litb 24.01.2009 17:55

вы может используете выделение стека для наиболее распространенных размеров итераторов, но рано или поздно вам придется иметь дело с размером итератора, который больше не подходит для небольшой оптимизации буфера, и тогда потребуется выделение кучи. boost :: any даже не буферизует это. он всегда использует кучу

Johannes Schaub - litb 24.01.2009 17:56

Я могу показать вам версию, которая точно соответствует вашим требованиям, но использует новые скрытые возможности (с использованием boost :: any и boost :: function). в любом случае, какого черта вы хотите избегать нового? вы можете показать нам пример, где это больно?

Johannes Schaub - litb 24.01.2009 18:00

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

El Zorko 24.01.2009 18:05

мм понятно. если у вас ограниченный набор контейнеров, вы можете использовать boost :: variant и здесь не выделять кучу. может быть, это был бы вариант?

Johannes Schaub - litb 24.01.2009 18:28

в противном случае я бы просто сначала использовал новый / удаленный материал и профиль, чтобы увидеть, действительно ли это вызывает какое-либо значительное замедление

Johannes Schaub - litb 24.01.2009 18:30

Я согласен с @litb; new / delete не может быть вашим узким местом. Вы также можете переопределить его, чтобы использовать стек. Если вам действительно нужна скорость, зачем вообще беспокоиться о MagicIterator? Кроме того, «преждевременная оптимизация - корень всех зол».

strager 24.01.2009 22:48

К сожалению, вы не можете повторно реализовать его для использования стека, не совершив чего-то плохого, как мое решение ниже. Мне нужна скорость, а также возможность перебирать X *, когда на самом деле базовая коллекция имеет Y, с подходящей абстракцией. Это для высокопроизводительного кода, в котором избыточные выделения кучи недопустимы.

El Zorko 27.01.2009 15:21

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

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

Это красиво кратко, но, к сожалению, у меня нет возможности увеличить скорость. Ужасно, я знаю, и это аккуратный ответ, который мне, возможно, придется попробовать в принципе, даже если я не могу его использовать в этом случае.

El Zorko 23.01.2009 12:44
Ответ принят как подходящий

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

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

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

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

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

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