Я ломал голову над этим последние сутки или около того. Я пытаюсь выяснить, как вернуть итератор из класса, скрывая тип контейнера, который используется классом. Например, у меня есть класс canvas, который содержит виджеты с тем же интерфейсом, и они хранятся в частном порядке в std :: vector. Так….
Упрощенный код
class Canvas
{
public:
WidgetIterator begin();
WidgetIterator end();
private:
class Impl;
std::unique_ptr<Impl> mImpl;
};
class Canvas::Impl
{
public:
std::vector<widget> mWidget;
//how to return mWidget.begin(), mWidget.end() up as widget iterator??
}
usage:
int main()
{
Canvas canvas();
...Fill canvas with widgets...
//iterate over widgets
for(auto& a_widget : canvas)
{
//do something with / to a_widget. User does not know or care
//that underlying iterator is std::vector<widget>::iterator
}
...Do more stuff....
return 0;
}
По сути, я хотел бы как-то использовать псевдонимы mWidget.begin () и mWidget.end () через Canvas :: begin () и Canvas :: end (). Пользователь знает, что итератор для виджета, ему просто не нужно знать, что итератор — это std :: vector :: iterator. Я пытаюсь скрыть свои реализации, используя PIMPL, и сохранить информацию о том, как вещи хранятся в классе.
Кажется, я не могу найти правильную «формулу». Я смотрел на стирание типов и пытался возвращать указатели на функции через интерфейс, но я не могу придумать способ удержать std :: vector :: iterator вне заголовка, и все, что я видел до сих пор в поискать это не похоже на то, что я пытаюсь сделать. Может ли кто-нибудь указать мне правильное направление? Материал для чтения? Есть понятие, которое я пропускаю? О-о, и я видел некоторые примеры использования, которые я не мог понять, как заставить работать в моей ситуации. Я хотел бы избежать этого, потому что я пытаюсь уменьшить внешние зависимости.
Заранее большое спасибо!!!
Самый простой способ удалить тип итератора — написать генератор входного итератора.
Этого достаточно для for(:)
цикл, но не все другие алгоритмы, и может легко обернуть любой контейнер произвольного доступа:
template<class T>
struct gen_iterator_t {
std::function<T(std::size_t)> f;
std::size_t n = 0;
gen_iterator& operator++() { ++n; return *this; }
gen_iterator operator++(int) { auto r = *this; ++*this; return r; }
T operator*() { return f(n); }
gen_iterator_t( gen_iterator_t const& )=default;
gen_iterator_t( gen_iterator_t && )=default;
gen_iterator_t& operator=( gen_iterator_t const& )=default;
gen_iterator_t& operator=( gen_iterator_t && )=default;
gen_iterator_t()=default;
explicit operator bool() const { return static_cast<bool>(f); }
gen_iterator_t( std::function<T(std::size_t)> fin, std::size_t i ):
f(fin), n(i)
{}
friend bool operator==( gen_iterator_t const& lhs, gen_iterator_t const& rhs ) {
if (!lhs && !rhs) return true;
if (!lhs || !rhs) return false;
return lhs.n == rhs.n;
}
friend bool operator==( gen_iterator_t const& lhs, gen_iterator_t const& rhs ) {
return !(lhs==rhs);
}
};
мы тогда пишем range
:
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
};
template<class It>
range_t<It> range( It s, It f ) { return {s,f}; }
и создать диапазон:
template<class F>
range_t< gen_iterator_t< std::result_of_t<F&(std::size_t)> > >
generate_range( F f, std::size_t start, std::size_t finish ) {
return { {f, start}, {f, finish} };
}
теперь ваш класс разоблачает
class Canvas {
public:
range_t< gen_iterator_t< widget& > > get_widgets();
};
который реализован как
range_t< gen_iterator_t< widget& > > Canvas::get_widgets() {
return generate_range(
[this]( std::size_t n )->widget& { return mImpl->mWidget[n]; },
0, mImpl->mWidget.size()
);
}
и мало что выставлено.
Если вы хотите пойти дальше и иметь возможность обернуть контейнеры с произвольным доступом, это немного больше работы.
Других решений пока нет …