Основанное на диапазоне выражение for (C++)
Циклически и последовательно выполняет оператор ( statement ) каждого элемента в выражении ( expression ).
Синтаксис
for ( Выражение объявления : диапазона )
инструкция
Замечания
Используйте инструкцию на основе for диапазона для создания циклов, которые должны выполняться через диапазон, который определяется как все, что можно итерировать, например, std::vector или любую другую последовательность стандартной библиотеки C++, диапазон которой определяется begin() и end() . Имя, объявленное в for-range-declaration части, является локальным for для инструкции и не может быть повторно объявлено в expression или statement . Обратите внимание, что auto ключевое слово предпочтительнее в for-range-declaration части инструкции.
Новые возможности Visual Studio 2017: циклы на основе for диапазона больше не требуют этого begin() и end() возвращают объекты одного типа. Это позволяет end() возвращать объект sentinel, например используемый диапазонами, как определено в предложении Ranges-V3. Дополнительные сведения см. в разделе «Обобщение цикла на основе For диапазона» и библиотеки range-v3 на GitHub.
В этом коде показано, как использовать циклы на основе for диапазона для итерации по массиву и вектору:
// range-based-for.cpp // compile by using: cl /EHsc /nologo /W4 #include #include using namespace std; int main() < // Basic 10-element integer array. int x[10] = < 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 >; // Range-based for loop to iterate through the array. for( int y : x ) < // Access by value using a copy declared as a specific type. // Not preferred. cout cout << endl; // The auto keyword causes type inference to be used. Preferred. for( auto y : x ) < // Copy of 'x', almost always undesirable cout cout << endl; for( auto &y : x ) < // Type inference by reference. // Observes and/or modifies in-place. Preferred when modify is needed. cout cout << endl; for( const auto &y : x ) < // Type inference by const reference. // Observes in-place. Preferred when no modify is needed. cout cout v; for (int i = 0; i < 10; ++i) < v.push_back(i + 0.14159); >// Range-based for loop to iterate through the vector, observing in-place. for( const auto &j : v ) < cout cout
Результат выглядит так:
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 end of integer array test 0.14159 1.14159 2.14159 3.14159 4.14159 5.14159 6.14159 7.14159 8.14159 9.14159 end of vector test
Цикл на основе for диапазона завершается при выполнении одного из них statement : a break return , или goto к помеченной инструкции за пределами цикла на основе for диапазона. Оператор continue в цикле на основе for диапазона завершает только текущую итерацию.
Имейте в виду эти факты о зависимости от for диапазона:
- Такие циклы автоматически распознают массивы.
- Такие циклы автоматически распознают контейнеры с методами .begin() и .end() .
- Для всех остальных итераторов в них используются поиск, зависящий от аргументов ( begin() и end() ).
Упрощаем for-цикл по индексам: range-based версия
Волею судеб мне довелось заняться одной задачей автоматизации при помощи Python-скрипта. Изучая базовые конструкции, наибольший интерес у меня вызвал следующий код:
for index in range(0,10) : do_stuff()
Удобно, читаемо, лаконично (модно, стильно, молодежно)! Почему бы не организовать такой же цикл в С++? Что из этого вышло — под катом.
Попытка первая — макросы
О недостатках макросов написано много. И главное правило гласит: «Если можно что-то реализовать не используя макросы — так и сделай». Но иногда использование макросов вполне оправданно.
Макросы часто используют для расширения языка нестандартными конструкциями — например, чтобы ввести ключевое слово вечного цикла для большей читаемости кода:
#define infinite_loop while(true) infinite_loop
Кстати, мы ведь тоже задались вопросом реализации нестандартного цикла. Что если попробовать реализовать это дело с помощью макросов. Примерно вот так:
#include #define ranged_for(var, min, max, step) for(auto var = (min); var < (max); var += (step) ) int main() < ranged_for(i, 0, 10, 1) < std::cout return 0; >
Конечно такой код свою задачу выполняет, но цель более чем не достигнута — вместо того, чтобы сделать код более читаемым и лаконичным, мы скорее еще больше запутали его.
- Нечитаемые имена. Макросы — это автозамена. Если использовать простые имена в названии и аргументах макроса, то велик шанс коллизий с пользовательским кодом. Показательный пример — коллизия макроса min\max из Windows.h с функциями стандартной библиотеки std::min\std::max. Поэтому часто приходится использовать нечитаемые имена во благо избежания описанной проблемы.
- Никакой перегрузки. Макросы — это автозамена. Если написать несколько макросов с одинаковым именем, то доступен будет только один и з них. Поэтому написать несколько версий одного и того же макроса нельзя. А нам бы хотелось чтоб прям совсем как в Python.
Попытка вторая — функция-генератор коллекции
Если внимательно почитать документацию по range() из Python, то можно увидеть, что range() генерирует список сразу всех значений из диапазона. Поступим точно так же и напишем функцию, которая будет возвращать std::vector где каждый элемент — это значение индекса:
template std::vector range(T min, T max, T step) < const bool is_unsigned = std::is_unsigned::value; if (is_unsigned && min > max) return std::vector(0); size_t size = size_t((max - min) / step); if (!is_unsigned && size < 0) return std::vector(); if (size == 0) return std::vector(1, min); std::vector values; values.reserve(size); if (step < 0) < for (T i = min; i >max; i += step) < values.push_back(i); >> else < for (T i = min; i < max; i += step) < values.push_back(i); >> return values; > template std::vector range(T min, T max) < return range(min, max, 1); > template std::vector range(T max) < return range(0, max); >
Учитывая новый синтаксис для перебора значений коллекции в стандарте С++11, возможно написать следующий код:
int main() < std::cout (10)) std::cout << i << ' '; std::cout << ']' << std::endl; std::cout (0, 10)) std::cout << i << ' '; std::cout << ']' << std::endl; std::cout (0, 10, 2)) std::cout << i << ' '; std::cout << ']' << std::endl; std::cout (10, 2)) std::cout << i << ' '; std::cout << ']' << std::endl; std::cout (10, 2, -1)) std::cout
Вооот, это уже похоже на то, чего мы хотим достигнуть. Теперь это читается как «По всем i в диапазоне от 0 до 10». Согласитесь, звучит лучше, чем «От i равного 0, пока меньше 10, увеличивать на 1». В итоге вывод программы будет следующим:
[0 1 2 3 4 5 6 7 8 9 ]
[0 1 2 3 4 5 6 7 8 9 ]
[0 2 4 6 8 ]
[]
[10 9 8 7 6 5 4 3 ]
Это решение имеет очевидный недостаток, который следует из определения, — чрезмерное для данной операции потребление ресурсов. И чем больше диапазон значений — тем больше ресурсов потребляет промежуточное звено. В Python для решения данной проблемы существует функция xrange(), которая позволяет генерировать значения на лету.
К сожалению, функции-генераторы нам недоступны, поэтому прийдется искать другое решение.
Попытка третья, финальная — псевдо-коллекция
Чтобы пользовательский класс-коллекция поддерживал проход с помощью range-based циклов необходимо всего нечего — реализовать функции begin() и end(), которые возвращают итераторы на начало и конец коллекции соответственно. Дополнительно необходимо реализовать класс самого итератора. Но что если реализовать класс, который коллекцией будет только на уровне интерфейса, но внутренняя реализация хранить все значения не будет, а сгенерирует их по мере необходимости?
Тогда упрощенная реализация нашего класса может выглядеть следующим образом:
template class range sealed < public: range(T _min, T _max, T _step = T(1)) : m_min(_min), m_max(_max), m_step(_step) < >T operator[](size_t index) < return (m_min + index * m_step); >size_t size() < return static_cast((m_max - m_min) / m_step); > range_iterator> begin() < return range_iterator>(this, m_min); > range_iterator> end() < return range_iterator>(this, m_min + size() * m_step); > private: T m_min; T m_max; T m_step; >;
Все, что необходимо хранить — это границы диапазона и шаг. Тогда любой элемент диапазона можно получить с помощью простой арифметики (см. operator[]). Основная же работа возлагается на класс итератора:
template class range_iterator sealed < public: typedef T range_type; typedef range_iteratorself_type; typedef typename range_type::value_type value_type; range_iterator(const range_type* const range, value_type start_value) : m_range(range), m_value(start_value) < >operator value_type() const < return m_value; >value_type& operator*() < return m_value; >self_type& operator++() < m_value += m_range->step(); return *this; > self_type operator++(int) < self_type tmp(*this); ++(*this); return tmp; >bool operator==(const self_type& other) const < return ((m_range == other.m_range) && (equals(m_value, other.m_value, m_range->step()))); > bool operator!=(const self_type& other) const < return !((*this) == other); >private: template static bool equals(R a, R b, R e) < return a == b; >template<> static bool equals(double a, double b, double e) < return std::abs(a - b) < std::abs(e); >template<> static bool equals(float a, float b, float e) < return std::abs(a - b) < std::abs(e); >const range_type* const m_range; value_type m_value; >;
Думаю, дополнительно стоит пояснить наличие функции equals(). Предположим у нас диапазон нецелочисленный, а, допустим, от 0 до 10 с шагом 0.1. Сравнение итераторов основано на сравнении текущих значений из диапазона, хранящихся в каждом из них. Но сравнивать числа с плавающей точкой в С++ просто так нельзя. Подробнее почему можно почитать вот здесь. Скажу лишь, что если сравнивать «в лоб», то скорее всего цикл будет бесконечным. Лучший способ — это сравнивать разницу с допустимой абсолютной погрешностью. Это и реализовано в функции equals(). При чем в нашем случае абсолютная погрешность — это шаг диапазона.
Вот теперь действительно можно написать цикл в необходимой нам форме и при этом не сильно тратиться на накладные расходы.
Полная версия кода:
range.h
template class range_iterator : std::iterator < public: typedef T range_type; typedef range_iteratorself_type; typedef std::random_access_iterator_tag iterator_category; typedef typename range_type::value_type value_type; typedef typename range_type::size_type size_type; typedef typename range_type::difference_type difference_type; typedef typename range_type::pointer pointer; typedef typename range_type::const_pointer const_pointer; typedef typename range_type::reference reference; typedef typename range_type::const_reference const_reference; range_iterator(const range_type* const range, value_type start_value) : m_range(range), m_value(start_value) < >range_iterator(const self_type&) = default; range_iterator(self_type&&) = default; range_iterator& operator=(const range_iterator&) = default; ~range_iterator() = default; operator value_type() const < return m_value; >value_type& operator*() < return m_value; >self_type& operator++() < m_value += m_range->step(); return *this; > self_type operator++(int) < self_type tmp(*this); ++(*this); return tmp; >self_type& operator--() < m_value -= m_range->step(); return *this; > self_type operator--(int) < self_type tmp(*this); --(*this); return tmp; >self_type operator+(difference_type n) < self_type tmp(*this); tmp.m_value += m_range->step() * n; return tmp; > self_type& operator+=(difference_type n) < m_value += n * m_range->step(); return (*this); > self_type operator-(difference_type n) < self_type tmp(*this); tmp.m_value -= n * m_range->step(); return tmp; > self_type& operator-=(difference_type n) < m_value -= n * m_range->step(); return (*this); > bool operator==(const self_type& other) const < return ((m_range == other.m_range) && (equals(m_value, other.m_value, m_range->step()))); > bool operator!=(const self_type& other) const < return !((*this) == other); >private: template static bool equals(T a, T b, T e) < return a == b; >template<> static bool equals(double a, double b, double e) < return std::abs(a - b) < std::abs(e); >template<> static bool equals(float a, float b, float e) < return std::abs(a - b) < std::abs(e); >const range_type* const m_range; value_type m_value; >; template class range sealed < static_assert(std::is_arithmetic::value, "Template type should be a integral-type"); public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; typedef range self_type; typedef class range_iterator iterator; typedef std::reverse_iterator reverse_iterator; range(value_type _min, value_type _max, value_type _step = value_type(1)) : m_min(_min), m_max(_max), m_step(_step) < if (m_step == 0) < throw std::invalid_argument("Step equals zero"); >> range(const self_type&) = default; range(self_type&&) = default; range& operator=(const self_type&) = default; ~range() = default; bool operator==(const self_type& _obj) const < return (m_max == _obj.max()) && (m_min == _obj.min()) && (m_step == _obj.step()); >bool operator!=(const self_type& _obj) const < return !(this == _obj); >value_type operator[](size_type _index) const < #ifdef _DEBUG if (_index >size()) < throw std::out_of_range("Index out-of-range"); >#endif return (m_min + (_index * m_step)); > bool empty() const < bool is_empty = ((m_max < m_min) && (m_step >0)); is_empty |= ((m_max > m_min) && (m_step < 0)); return is_empty; >size_type size() const < if (empty()) < return 0; >return static_cast((m_max - m_min) / m_step); > value_type min() const < return m_min; >value_type max() const < return m_max; >value_type step() const < return m_step; >iterator begin() const < iterator start_iterator(this, m_min); return start_iterator; >iterator end() const < iterator end_iterator(this, m_min + size() * m_step); return end_iterator; >reverse_iterator rbegin() const < reverse_iterator start_iterator(end()); return start_iterator; >reverse_iterator rend() const < reverse_iterator end_iterator(begin()); return end_iterator; >private: value_type m_min; value_type m_max; value_type m_step; >;
Range based for c что это
Уважаемые знатоки стандарта!
Будьте так добры, подскажите, что в нижеследующих фрагментах кода неверно в части использования циклов for?
#include #include int main() < typedef std::vectorint> V; V v = ; for (auto i: V(v)=V(v)) < std::cout return 0; >
#include #include int main() < typedef std::vectorint> V; V v = ; for (auto i: V(v)) < std::cout return 0; >
Re: range based for loop
| От: | Кодт | |
| Дата: | 29.06.14 18:33 | |
| Оценка: | 24 (1) | |
Здравствуйте, D14, Вы писали:
D14>Уважаемые знатоки стандарта!
D14>Будьте так добры, подскажите, что в нижеследующих фрагментах кода неверно в части использования циклов for?
D14> for (auto i: V(v)=V(v))
Здесь создаются два временных объекта — копии исходного вектора v — и немедленно одна присваивается второй.
То есть, если записать функционально,
for(auto i : V::operator=( V(v), V(v) ) )
результат присваивания — ссылка на левый операнд.
Время жизни операндов — до конца полного выражения, то есть, они немедленно сдохли оба, а шапка цикла запомнила дохлую ссылку.
Поскольку в ходе деструкции вектор только освобождает блок памяти под массив данных, менеджер кучи пишет в его начало какие-то свои полезняшки.
Что мы и наблюдаем в выводе:
139550768 2 3
D14> for (auto i: V(v))
Здесь всё прозрачно и спокойно: создаётся временный объект — копия исходного v, а время его жизни продлевается до конца цикла.
Перекуём баги на фичи!
Re[2]: range based for loop
| От: | D14 |
| Дата: | 30.06.14 13:12 |
| Оценка: |
Здравствуйте, Кодт, Вы писали:
К>результат присваивания — ссылка на левый операнд.
Внимание вопрос(c): static_cast(. ) в обоих случаях это ссылка на временный объект? Если нет, то почему?
Re[3]: range based for loop
| От: | Кодт |
| Дата: | 30.06.14 15:01 |
| Оценка: |
Здравствуйте, D14, Вы писали:
D14>Внимание вопрос(c): static_cast(. ) в обоих случаях это ссылка на временный объект? Если нет, то почему?
Где это в обоих случаях static_cast написан? Его там нет, поэтому вопрос ни о чём.
Перекуём баги на фичи!
Re[4]: range based for loop
| От: | D14 |
| Дата: | 01.07.14 07:30 |
| Оценка: |
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, D14, Вы писали:
D14>>Внимание вопрос(c): static_cast(. ) в обоих случаях это ссылка на временный объект? Если нет, то почему?
К>Где это в обоих случаях static_cast написан? Его там нет, поэтому вопрос ни о чём.
Как где? Вот же
int main() < typedef std::vectorV; V v = ; for (auto i: static_cast(V(v)=V(v))) < std::cout return 0; >
int main() < typedef std::vectorV; V v = ; for (auto i: static_cast(V(v))) < std::cout return 0; >
Re[5]: range based for loop
| От: | Кодт | |
| Дата: | 01.07.14 08:46 | |
| Оценка: | 16 (1) | |
Здравствуйте, D14, Вы писали:
D14> for (auto i: static_castconst V&>(V(v)=V(v))) D14> for (auto i: static_castconst V&>(V(v)))
Ровно та же история. В обоих случаях static_cast ничего не меняет.
V foo(); V const& bar(); V const& x = foo() ; // продлевает жизнь временного объекта V const& y = static_castconst&>(foo()); // static_cast не играет изолирующей роли V const& z = bar() ; // запоминает ссылку, какова бы ни была её природа V const& t = static_castconst&>(bar()); // очевидно, что static_cast здесь вообще ничего не делает
А внутри for подразумевается следующее (6.5.4 The range-based for statement [stmt.ranged])
for(VAR_TYPE VAR_NAME : RANGE_EXPR) BODY; //// = auto&& _range_ = RANGE_EXPR; for(auto _iter_ = begin(_range_), _end_ = end(_range_); _iter_ != _end_; ++_iter_)
Есть небольшая тонкость с тем, во что превращается auto&&, но на время жизни это не влияет.
Перекуём баги на фичи!
Re[6]: range based for loop
| От: | D14 |
| Дата: | 01.07.14 09:31 |
| Оценка: |
Здравствуйте, Кодт, Вы писали:
К>Здравствуйте, D14, Вы писали:
К>
D14>> for (auto i: static_castconst V&>(V(v)=V(v))) D14>> for (auto i: static_castconst V&>(V(v))) К>
К>Ровно та же история. В обоих случаях static_cast ничего не меняет.
Ну, мне в общем-то понятно, что та же. Меня-то интересует, есть ли какое-нибудь каноническое описание,
где дескрализировано знание, что подобный нижеследующему код некорректен? Ибо * из временного объекта делает l-value?
typedef std::vector V; std::unique_ptr foo() < return std::unique_ptr(new V); > int main() < for (auto i: *foo()) < std::cout return 0; >
Re[7]: range based for loop
| От: | Кодт |
| Дата: | 01.07.14 11:18 |
| Оценка: |
Здравствуйте, D14, Вы писали:
D14>Ну, мне в общем-то понятно, что та же. Меня-то интересует, есть ли какое-нибудь каноническое описание,
D14>где дескрализировано знание, что подобный нижеследующему код некорректен? Ибо * из временного объекта делает l-value?
В стандарте, где говорится о времени жизни объектов.
3.10 Lvalues and rvalues [basic.lval]
8.5.3 References [dcl.init.ref]
12.2 Temporary objects [class.temporary]
Это достаточно муторные главы, но если через них продраться, то понимание произойдёт.
Если коротко, то *foo() возвращает какую-то ссылку, полученную из метода временного объекта.
Компилятору нет нужды считать, что вот эта ссылка — на ещё один временный объект. А не на что-то постороннее.
Чтобы такого не было, надо делать адаптер, хотя бы вот такой
#include #include #include using namespace std; typedef vectorint> V; typedef unique_ptr P; templateclass PRange> struct indirector < PRange p_; indirector(PRange&& p) : p_(move(p)) <>// для соответствия модели range based for auto begin() const -> decltype(p_->begin()) < return p_->begin(); > auto end () const -> decltype(p_->end ()) < return p_->end (); > >; templateclass PRange> indirector indirect(PRange&& p) < return move(p); > P foo() < return P(new V); > int main() < for(auto i : *foo()) // навь : указатель временный и разрушен, указуемый разрушен указателем cout "---" for(auto i : indirect(foo())) // правь : указатель заначен во временном индиректоре, индиректор продлён cout
На выходе, ожидаемо,
158326820 какой-то мусор (и нам ещё повезло, что всего 5 итераций вышло) 17 0 2 3 --- 1 честное содержимое вектора 2 3
Qt/C++ - Урок 079. foreach или range-based циклы for в C++11?
В Qt имеется своё ключевое слово foreach для итерации по элементам контейнеров. Данное ключевое слово было введено ещё до стандарта C++11 и является макросом. На данный момент в стандарте C++11 присутствуют range-based циклы for , которые выполняют такой же функционал, как и foreach . Но в обоих случаях есть свои нюансы. Давайте разберёмся.
К каким объектам могут быть применены
foreach и range-based for работают с контейнерами, которые имеют итератор. В данном случае различий нет. Их можно применить только к объектам с итератором.
Применение, программный код
Посмотрим на программный код. Допустим у нас имеется некий контейнер
QList strings;И мы хотим что-то сделать с элементами этого контейнера, тогда запись для foreach будет выглядеть так
foreach (QString& str, strings) < // ToDo something >а для range-based for запись будет следующей
for (QString& str : strings) < // ToDo something >Препроцессор
Ключевое слово foreach является макросом, который в итоге будет эквивалентен вот такой записи
for (QList::iterator it = strings.begin(); it != strings.end(); ++it) < QString &str = *it; // ваш код >Но поскольку это макрос, то получаем оверхед по времени компиляции на обработку файлов препроцессором, который будет разворачивать все эти макросы в рабочий код.
Скорость работы
foreach работает медленнее range-based for циклов, поскольку выполняет предварительное копирование контейнера, о чём говорится в документации Qt. Из копирования вытекает следующий нюанс, что если во время работы foreach вы что-то сделаете с контейнером, то это не будет иметь эффекта на элементы в цикле. При этом неявное копирование осуществляется и тогда, когда вы не модифицируете элементы контейнера. Подобное поведение кода может быть довольно затратным для STL контейнеров, которые дороги в копировании. Поэтому уже по этой причине лучше использовать range-based for циклы из стандарта C++11.
Недостатки range-based for
Если вчитаться в документацию Qt, то можно обнаружить, что они рекомендуют всё-таки использовать foreach для контейнеров Qt, а для всех остальных (STL, Boost и т.д.) использовать range-based for циклы. Мотивируется это тем, что контейнер с разделяемыми данными при работе range-based for цикла может быть отсоединён форсированно (detach) от этих общих разделяемых данных и при этом не будет возвращён к исходному состоянию, после выполнения действий, в результате чего может произойти повреждение данных. Одним из таких классов неявно разделямых классов является QPen, который использует глубокое копирование при отсоединении (detach).
void QPen::setStyle(Qt::PenStyle style) < detach(); // detach from common data d->style = style; // set the style member > void QPen::detach() < if (d->ref != 1) < . // perform a deep copy >>Заключение
По своей практике я не сталкивался с такими проблемами при использовании range-based for циклов для контейнеров Qt, полагаю, возможно это связано с тем, что не приходилось часто использовать подобные циклы для контейнеров с объектами QPen или иными классами Qt требующими отделения от разделяемых данных. По факту последнее время использование контейнеров STL мне кажется более удобным, чем контейнеров Qt в связи с использование функторов (лямбд) для поиска (std:;find, std::find_if алгоритмы и т.д.) конкретных объектов в контейнере, вместо обычно цикла с условием в нём. Также придерживаюсь точки зрения, что лучше использовать конструкции языка вместо МАКРОСОВ. Просто нужно учитывать, что могут быть проблемы, в связи с особенностями контейнеров и других классов Qt. А так итоговое решение по использованию foreach vs range-based for оставляю на ваше усмотрение.
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
По статье задано0 вопрос(ов)
