Evgenii Legotckoi
15 мая 2018 г. 12:26

Qt/C++ - Урок 079. foreach или range-based циклы for в C++11?

В Qt имеется своё ключевое слово foreach для итерации по элементам контейнеров. Данное ключевое слово было введено ещё до стандарта C++11 и является макросом. На данный момент в стандарте C++11 присутствуют range-based циклы for , которые выполняют такой же функционал, как и foreach .

Но в обоих случаях есть свои нюансы. Давайте разберёмся.


К каким объектам могут быть применены

foreach и range-based for работают с контейнерами, которые имеют итератор. В данном случае различий нет. Их можно применить только к объектам с итератором.

Применение, программный код

Посмотрим на программный код. Допустим у нас имеется некий контейнер

QList<QString> strings;

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

foreach (QString& str, strings)
{
    // ToDo something
}

а для range-based for запись будет следующей

for (QString& str : strings)
{
    // ToDo something
}

Препроцессор

Ключевое слово foreach является макросом, который в итоге будет эквивалентен вот такой записи

for (QList<QString>::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 оставляю на ваше усмотрение.

Рекомендуемые статьи по этой тематике

По статье задано0вопрос(ов)

3

Вам это нравится? Поделитесь в социальных сетях!

a
  • 10 апреля 2020 г. 10:41

Since Qt 5.7 the foreach macro is deprecated, Qt encourages you to use the C++11 for instead.
http://doc.qt.io/qt-5/qtglobal.html#foreach

(more details about the difference here : https://www.kdab.com/goodbye-q_foreach/ )

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь