У меня есть следующий код, представляющий сетку в 3D-приложении (некоторый код опущен для ясности):
class Mesh {
public:
typedef std::vector<Vertex> Vertices;
typedef std::vector<int> Elements;
template<class VerticesIt, class ElementsIt>
Mesh(const VerticesIt verticesBegin,
const VerticesIt verticesEnd,
const ElementsIt elementsBegin,
const ElementsIt elementsEnd) :
vertices_(verticesBegin, verticesEnd),
elements_(elementsBegin, elementsEnd) {
}
virtual ~Mesh();
typename Vertices::const_iterator verticesBegin() const {
return vertices_.begin();
};
typename Vertices::const_iterator verticesEnd() const {
return vertices_.end();
};
typename Elements::const_iterator elementsBegin() const {
return elements_.begin();
};
typename Elements::const_iterator elementsEnd() const {
return elements_.end();
};
private:
Vertices vertices_;
Elements elements_;
};
Это работает довольно хорошо, предоставляя понятный интерфейс для внутренних данных. Никакие детали реализации не выставляются для контейнеров.
У меня есть один маленький сбой по этому поводу, хотя. Я не могу использовать диапазон, основанный на циклах, итераторы должны быть использованы:
for (auto it = mesh.verticesBegin(); it != mesh.verticesEnd(); ++it) {
// Do stuff
}
for (auto it = mesh.elementsBegin(); it != mesh.elementsEnd(); ++it) {
// Do stuff
}
Немного многословно на мой вкус. Мой предпочтительный код клиента вместо этого будет выглядеть так:
for(auto & vert : mesh.vertices) {
// Do stuff.
}
for(auto & element : mesh.elements) {
// Do stuff.
}
Можно ли этого добиться без раскрыть детали реализации контейнеров? Кроме того, я не хотел бы заключать контейнеры в пользовательские классы, так как мне нужен полный доступ к выбранному контейнеру (std :: vector) внутри класса Mesh.
Вы можете использовать какой-нибудь прокси, такой как этот:
template<typename Container>
class ConstIteratorProxy
{
public:
ConstIteratorProxy(const Container& container) : container_(container) { }
typename Container::const_iterator begin() const {
return container_.begin();
};
typename Container::const_iterator end() const {
return container_.end();
};
private:
const Container& container_;
};
И в сетке:
ConstIteratorProxy<Vertices> vertices() const {
return ConstIteratorProxy<Vertices>(vertices_);
}
ConstIteratorProxy<Elements> elements() const {
return ConstIteratorProxy<Elements>(elements_);
}
Тогда использовать это:
Mesh m;
for (auto& v : m.vertices()) { }
for (auto& e : m.elements()) { }
Переименуйте свои функции verticesBegin
а также verticesEnd
в begin
а также end
соответственно. Тогда вы сможете написать это:
for(auto & vert : mesh)
{
// Do stuff.
}
Обратите внимание, что диапазон основан for
цикл ожидает, что ваш контейнер содержит begin
а также end
как функции-члены — или свободные функции begin
а также end
который принимает ваш контейнер в качестве аргумента.
Также обратите внимание, что основанный на диапазоне цикл for не может перебирать две вещи: mesh
может вести себя как один контейнер, а не как два. Так что, если вы хотите перебирать и индексы, то вы должны выставить их или абстрагировать, а затем вместо этого выставить вершины.
Или вы можете написать zip-итератор, который будет перебирать оба контейнера одновременно в одном цикле for.
Это довольно просто с прокси-сервером, хранящим два итератора с реализованным методом begin и end:
#include <iostream>
#include <vector>
template <typename T>
struct PairIt {
PairIt(T&& b, T&& e) : b_{std::forward<T>(b)}, e_{std::forward<T>(e)} {}
T begin() const { return b_; }
T end() const { return e_; }
private:
T b_,e_;
};
std::vector<int> badglobal { 1, 2, 3 };
PairIt<std::vector<int>::iterator> ForRangeProxy() {
return { begin(badglobal), end(badglobal) };
};
int main() {
for( auto v : ForRangeProxy() )
std::cout << v << std::endl;
}
Вы можете реализовать несколько ForRangeProxy
для каждой коллекции ваш объект хочет предоставить доступ.
Вы можете вернуть упаковщик итератора из vertices()
а также elements()
функции, которые вы можете передать в цикл for, например,
template<typename T>
class iterator_wrapper
{
public:
iterator_wrapper(const typename std::vector<T>::const_iterator &begin_,
const typename std::vector<T>::const_iterator &end_)
:m_begin(begin_), m_end(end_) {}
typename std::vector<T>::const_iterator begin() const {return m_begin;}
typename std::vector<T>::const_iterator end() const {return m_end;}
private:
typename std::vector<T>::const_iterator m_begin, m_end;
};
определять vertices()
а также elements()
функции в вашем классе сетки:
iterator_wrapper<Vertex> vertices() {return iterator_wrapper<Vertex>(vertices_.begin(), vertices_.end());}
iterator_wrapper<int> elements() {return iterator_wrapper<int>(elements_.begin(), elements_.end());}
Тогда назовите это как:
for(auto &vert:mesh.vertices())
{
//....
}
for(auto &element:mesh.elements())
{
//....
}