Цикл C ++ 11 на основе диапазона: получить элемент по значению или ссылку на const

215

Читая некоторые примеры циклов на основе диапазона, они предлагают два основных способа 1 , 2 , 3 , 4

std::vector<MyClass> vec;

for (auto &x : vec)
{
  // x is a reference to an item of vec
  // We can change vec's items by changing x 
}

или

for (auto x : vec)
{
  // Value of x is copied from an item of vec
  // We can not change vec's items by changing x
}

Хорошо.

Когда нам не нужно менять vecпредметы, IMO, Примеры предлагают использовать вторую версию (по значению). Почему они не предлагают что-то, что constссылки (по крайней мере, я не нашел прямого предложения):

for (auto const &x : vec) // <-- see const keyword
{
  // x is a reference to an const item of vec
  // We can not change vec's items by changing x 
}

Разве это не лучше? Разве это не позволяет избежать избыточной копии в каждой итерации, пока она const?

Масуд
источник

Ответы:

390

Если вы не хотите , чтобы изменить элементы, а также хотят , чтобы избежать создания копий, то auto const &это правильный выбор:

for (auto const &x : vec)

Тот, кто предлагает вам использовать auto &это неправильно. Игнорируй их.

Вот резюме:

  • Выберите, auto xкогда вы хотите работать с копиями.
  • Выберите, auto &xкогда вы хотите работать с оригинальными элементами и можете изменять их.
  • Выберите, auto const &xкогда вы хотите работать с оригинальными элементами и не будете изменять их.
Наваз
источник
21
Спасибо за отличный ответ. Я думаю, это также должно быть указано, что const auto &xэквивалентно вашему третьему выбору.
смг
7
@mloskot: это эквивалентно. (и что вы подразумеваете под «но это тот же синтаксис» ? Синтаксис заметно различается.)
Наваз
14
Отсутствует: auto&&когда вы не хотите делать ненужную копию, и вам все равно, измените ли вы ее или нет, и вы хотите просто работать.
Якк - Адам Невраумонт
4
Применяется ли различие в ссылках к копиям, если вы просто работаете с фундаментальными типами, такими как int / double?
4
@racarate: я не могу комментировать общую скорость, и я думаю, что никто не может, без предварительного профилирования. Честно говоря, я не буду основывать свой выбор на скорости, а на ясности кода. Если я хочу неизменности, я бы использовал constнаверняка. Тем не менее, будь то auto const &, или auto const будет мало разницы. Я бы выбрал auto const &просто быть более последовательным.
Наваз
23

Если у вас есть std::vector<int>или std::vector<double>, тогда лучше использовать autoвместо него (со значением copy) const auto&, так как копирование intили это doubleдешево:

for (auto x : vec)
    ....

Но если у вас есть std::vector<MyClass>, где MyClassесть некоторая нетривиальная семантика копирования (например std::string, некоторый сложный пользовательский класс и т. Д.), Тогда я бы предложил использовать, const auto&чтобы избежать глубоких копий :

for (const auto & x : vec)
    ....
Mr.C64
источник
3

Когда нам не нужно менять vecэлементы, Примеры предлагают использовать первую версию.

Тогда они дают неверное предложение.

Почему они не предлагают то, что постоянные ссылки

Потому что они дают неверное предложение :-) То, что вы упоминаете, является правильным. Если вы хотите только наблюдать за объектом, вам не нужно создавать копию, и нет необходимости иметь constссылку на него.

РЕДАКТИРОВАТЬ:

Я вижу, что ссылки, на которые вы ссылаетесь, содержат примеры итерации по диапазону intзначений или некоторому другому фундаментальному типу данных. В этом случае, поскольку копирование не intявляется дорогостоящим, создание копии в основном эквивалентно (если не более эффективно, чем) наблюдению const &.

Однако в общем случае это не относится к пользовательским типам. Копирование UDT может быть дорогостоящим, и если у вас нет причины для создания копии (например, изменение извлеченного объекта без изменения исходного), то предпочтительно использоватьconst & .

Энди Проул
источник
1

Я собираюсь быть противоположным здесь и сказать, что нет необходимости auto const &в диапазоне, основанном на цикле. Скажите, если вы думаете, что следующая функция глупа (не по назначению, а по тому, как она написана):

long long SafePop(std::vector<uint32_t>& v)
{
    auto const& cv = v;
    long long n = -1;
    if (!cv.empty())
    {
        n = cv.back();
        v.pop_back();
    }
    return n;
}

Здесь автор создал константную ссылку на vиспользование для всех операций, которые не изменяют v. На мой взгляд, это глупо, и такой же аргумент можно использовать для использования auto const &в качестве переменной в диапазоне, основанном на цикле вместо просто auto &,

Бенджамин Линдли
источник
3
@BenjaminLindley: Итак, я могу сделать вывод, что вы также будете спорить const_iteratorв цикле for? Как вы гарантируете, что не меняете исходные элементы из контейнера, когда выполняете итерации по нему?
Наваз
2
@BenjaminLindley: Почему ваш код не компилируется без const_iterator? Позвольте мне угадать, контейнер, над которым вы перебираете, есть const. Но с чего это constначать? Где вы используете, constчтобы убедиться, что?
Наваз
8
Преимущество создания объектов constподобно преимуществам использования private/ protectedв классах. Это позволяет избежать дальнейших ошибок.
Масуд
2
@BenjaminLindley: О, я упустил это. Тогда и в этом случае реализация тоже глупая. Но если бы эта функция не изменялась v, реализация была бы хорошей IMO. И если цикл никогда не изменяет объекты, через которые он проходит, мне кажется правильным иметь ссылку на const. Я понимаю вашу точку зрения, хотя. Мое состоит в том, что цикл представляет кодовую единицу почти так же, как функция, и внутри этой единицы нет инструкции, которая должна изменить значение. Таким образом, вы можете «пометить» весь блок так же const, как вы бы сделали с constфункцией-членом.
Энди Prowl
1
Таким образом, вы думаете, что const &основанный на диапазоне - это глупо, потому что вы написали неуместный воображаемый пример воображаемого программиста, который сделал что-то глупое с cv -qualification? ... Хорошо, тогда
underscore_d