Поиграв с этим, я подозреваю, что это невозможно, но я подумал, что спрошу экспертов. У меня есть следующий код на 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 (например, каким-то образом автоматически разрешается компилятором).
new(), ни malloc() на любом этапе), ни memcpy().
Спасибо за уделенное время, даже если вы просто читаете; это действительно меня беспокоит!
Обновлять: Хотя у меня было несколько очень интересных ответов, ни один из них еще не удовлетворил всем вышеперечисленным требованиям. Примечательно, что к сложным областям относятся: i) каким-то образом отделить MagicIterator от контейнера (аргументы шаблона по умолчанию не отсекают его) и ii) избежать выделения кучи; но я действительно ищу решение, которое охватывает все вышеперечисленные пункты.
Указатель должен быть получен путем вызова Object :: GetInterface (), поэтому на него нельзя полагаться как на член класса.
MagicIterator не может получить доступ к внутренним компонентам контейнера или не должен?
В идеале не могу, я надеялся на какую-то форму решения на основе адаптера (/ handwave), где MagicIterator не обязательно должен быть специфичным для контейнера. Так что это не хакерский класс, а тот, который можно повторно использовать в других ситуациях, чтобы абстрагироваться от того, что именно повторяется.





Я не вижу причин, по которым вы не можете реализовать это именно так, как вы это изложили ... Я что-то упускаю?
Чтобы уточнить, вам нужно поместить какие-то методы доступа в свой класс контейнера. Они могут быть частными, и вы можете объявить MagicIterator своим другом, если вы считаете, что это лучший способ инкапсулировать его, но я бы раскрыл их напрямую. Эти методы доступа будут использовать обычный итератор STL внутри контейнера и выполнять преобразование в интерфейс IInterface. Таким образом, итерация будет фактически выполняться с помощью методов доступа контейнера, а MagicIterator будет просто своего рода прокси-объектом, чтобы упростить задачу. Чтобы сделать его реентерабельным, вы можете передать MagicIterator какой-то идентификатор для поиска итератора STL внутри контейнера, или вы могли бы передать его в итератор STL как void *.
Я не хочу делать Container :: Item общедоступным. Я хочу, чтобы итератор не имел внешнего отношения к std :: list :: iterator; чтобы казалось, что он выполняет итерацию по набору указателей IInterface *, а Item остается полностью закрытым для контейнера. IInterface * происходит от Item.pObject-> GetInterface ().
мое решение не требует, чтобы вы делали элемент общедоступным. Итератор внешне не связан. Аксессоры обертывают std :: list :: iterator, поэтому только внутренняя часть контейнера знает, что это реализация. Вот почему я предложил использовать void *, чтобы передать его, чтобы сделать его реентерабельным.
Извините, это была опечатка. [Это, как вы, наверное, заметили, я здесь впервые. Очень жаль!] Я действительно хочу, чтобы MagicIterator был отделен от контейнера; Я хотел бы иметь возможность повторно использовать его в других произвольных классах, не меняя его, даже если, скажем, у других должен быть клей, чтобы он работал.
ну, вы могли бы заставить Контейнер реализовывать некоторый интерфейс, который предоставляет соответствующие методы доступа и, таким образом, иметь возможность повторно использовать MagicIterator с чем-либо еще, что реализует этот интерфейс, но кроме этого, у меня нет идей ... извините.
Некоторая форма IMagicIteratable <IInterface *> ... это мысль. Но как написать методы доступа в контейнере таким образом, чтобы использовать выделенный в стеке итератор std :: list <Item>, не подвергая его (и частный класс Item) вызывающей стороне указанных методов?
В моем предложении не было выделенного в стеке std :: list :: iterator. итератор находился внутри контейнера (или в нетипизированном указателе внутри класса MagicIterator). в любом случае Container будет действовать как фабрика для MagicIterator, таким образом, он может создать итератор внутри себя или MagicIterator.
Рискуя показаться глупым, если MagicIterator обращается к реальному итератору через нетипизированный указатель, где этот итератор? Я действительно хочу, чтобы это было где-то в стеке. Этого не может быть, например. список в контейнере, конечно, так как я не хочу выделения кучи во время итерации ... и я точно так же не хочу его new ().
Это действительно зависит от 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) ...
Да, основная задача - скрыть тот факт, что мы действительно используем std :: vector :: iterator <Item>, и сделать так, чтобы он выглядел так, как будто мы перебираем указатели IInterface * (полученные из объекта в каждом элементе) . В частности, Item должен оставаться закрытым для контейнера, в отличие от Object и IInterface.
Создайте абстрактный класс 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), не зная конкретного типа.
Его можно создать. Ваши классы begin () и end () создают его экземпляры. Я не вижу в этом проблемы.
for (Iterator <Object *> i = c.begin (); i! = c.end (); i ++) ... не будет компилироваться, поскольку Iterator <> является абстрактным.
Ах, ты прав. Блин, и я думал, что у меня что-то работает. =] У вас мог есть оболочка вокруг абстрактного класса, и вы передаете обернутый класс конструктору объекта Iterator. Я думаю, это сработает.
Собственно, если присмотреться, этого не пойдет; причина, по которой я этого не сделал, заключается в том, что в моем случае у меня не может быть распределения кучи, вызванного итерацией, поэтому "новый" отсутствует. alloca () (возможно, через зловеще перегруженный оператор new) тоже не поможет, так как он выйдет за пределы области видимости, когда ObjectContainer :: begin () вернется.
Звучит не слишком сложно. Вы можете определить итератор снаружи. Вы также можете использовать 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, вот для чего друзья.
Эль, Зорько. итератор - друг Container. поэтому он должен видеть элемент, даже если он частный
Эль, Зорько. если вы хотите отделить итератор от контейнера, сделайте контейнер также параметром шаблона и подтвердите, что «каждый контейнер должен иметь typedef, который объявляет InputIterator, обернутый как« wrapped_iterator », например. Затем вы можете выполнить Container :: Item, чтобы получить тип элемента тоже
(и по умолчанию для параметра InputIterator значение Container :: wrapped_iterator или полностью удалите его)
Эй, Зорько. О, это интересно. Комо также отвергает это. аргументы по умолчанию не кажутся благословенными дружбой. мне придется кое-что поменять :)
При ближайшем рассмотрении, это, к сожалению, тоже не совсем соответствует требованиям. MagicIterator должен принимать аргумент шаблона, указывающий его контейнер и реальный итератор, который напрямую связывает его с классом COntainer. Мне нужен развязанный MagicIterator для работы с несколькими классами в стиле контейнера.
Е.И. Зорько, это невозможно без новых. тогда ваш итератор должен быть полиморфным. но ты сказал, что не хочешь этого.
вы может используете выделение стека для наиболее распространенных размеров итераторов, но рано или поздно вам придется иметь дело с размером итератора, который больше не подходит для небольшой оптимизации буфера, и тогда потребуется выделение кучи. boost :: any даже не буферизует это. он всегда использует кучу
Я могу показать вам версию, которая точно соответствует вашим требованиям, но использует новые скрытые возможности (с использованием boost :: any и boost :: function). в любом случае, какого черта вы хотите избегать нового? вы можете показать нам пример, где это больно?
Приносим извинения, но я руководил постом с заявлением, что подозревал, что это невозможно. Этот код предназначен для облегчения итерации в среде с жесткими ограничениями, где минимизация использования распределения кучи очень важна.
мм понятно. если у вас ограниченный набор контейнеров, вы можете использовать boost :: variant и здесь не выделять кучу. может быть, это был бы вариант?
в противном случае я бы просто сначала использовал новый / удаленный материал и профиль, чтобы увидеть, действительно ли это вызывает какое-либо значительное замедление
Я согласен с @litb; new / delete не может быть вашим узким местом. Вы также можете переопределить его, чтобы использовать стек. Если вам действительно нужна скорость, зачем вообще беспокоиться о MagicIterator? Кроме того, «преждевременная оптимизация - корень всех зол».
К сожалению, вы не можете повторно реализовать его для использования стека, не совершив чего-то плохого, как мое решение ниже. Мне нужна скорость, а также возможность перебирать X *, когда на самом деле базовая коллекция имеет Y, с подходящей абстракцией. Это для высокопроизводительного кода, в котором избыточные выделения кучи недопустимы.
Я думаю, у вас есть две отдельные проблемы:
Сначала создайте итератор, который будет возвращать 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.
Это красиво кратко, но, к сожалению, у меня нет возможности увеличить скорость. Ужасно, я знаю, и это аккуратный ответ, который мне, возможно, придется попробовать в принципе, даже если я не могу его использовать в этом случае.
Теперь я нашел решение, которое подходит для моей первоначальной цели. Но мне все равно это не нравится :)
Решение включает в себя шаблон 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 () ' г).
Посетитель может быть более простым (и, следовательно, более простым в обслуживании) решением.
"... которые могут быть преобразованы в указатели интерфейса". означает ли это, что другой класс является базовым? или вы человек, фактический указатель является членом класса?