Привет всем, у меня такой вопрос. Есть обычный vector<int> и нужно просто вывести на консоль его элементы.
vector<int> test = {1, 2, 3, 4, 5};
for (int t: test){
cout << t << " ";
}
В цикле for вместо int часто указывают auto, тогда компилятор сам определяет тип переменной t. Но оказывается, вместо int можно также указать ссылку int& — у кого-то я видел такое, и в моем примере это нормально работает. Для надежности я указал const int&, и у меня в данном примере это тоже нормально работает.
Все же, как лучше делать, в каких случаях и почему?
Здравствуйте, Lanjeron32, Вы писали:
L>В цикле for вместо int часто указывают auto, тогда компилятор сам определяет тип переменной t. Но оказывается, вместо int можно также указать ссылку int& — у кого-то я видел такое, и в моем примере это нормально работает. Для надежности я указал const int&, и у меня в данном примере это тоже нормально работает. L>Все же, как лучше делать, в каких случаях и почему?
Если int, в переменную кладётся копия значения из вектора.
Если int&, то переменная это собственно элемент вектора. Можно при этом заменить её значение. Например, ++t в цикле — увеличит все элементы вектора на 1.
Если const int&, то тоже переменная это элемент вектора, но менять её будет нельзя.
Для int это по сути будет без разницы, если используешь t без изменения — int это просто значение, а его копирование дёшево "как 2 байта переслать" (ну, тут обычно 4, но эффект тот же).
А вот если бы это был объект, копирование которого было бы дорогой операцией — разница была бы в быстродействии.
А если копирование переносит реальное значение — то могло бы дать и разрушение сути содержимого вектора.
Поэтому для чего-то сложного — надо тщательно выбирать, какой вариант применять.
Как привычку по умолчанию для таких итераторов надо брать "const тип&", именно из-за минимизации эффектов.
А другие формы использовать только если уверен в том, что нужны именно они.
Здравствуйте, netch80, Вы писали:
N>А другие формы использовать только если уверен в том, что нужны именно они.
Спасибо за разъяснение, но хотелось бы выработать для себя практические правила, и в дальнейшем всегда следовать им в таких случаях. Мне думается, что нужно выбрать один из двух вариантов (прошу поправить, если я ошибаюсь).
1. Для vector<int> v всегда использовать for (int t: v), и не заморачиваться с const int&, поскольку выигрыша по сравнению с копированием значения практически нет. То же самое делать для float и double. Однако уже для vector<string> v всегда применять for (const string& s: v). Для сложных пользовательских типов (class, struct) — тем более.
2. Или же вообще не усложнять себе жизнь, а для этого в любых случаях всегда применять for (auto t: v).
Здравствуйте, Lanjeron32, Вы писали:
N>>А другие формы использовать только если уверен в том, что нужны именно они. L>Спасибо за разъяснение, но хотелось бы выработать для себя практические правила, и в дальнейшем всегда следовать им в таких случаях. Мне думается, что нужно выбрать один из двух вариантов (прошу поправить, если я ошибаюсь). L>1. Для vector<int> v всегда использовать for (int t: v), и не заморачиваться с const int&, поскольку выигрыша по сравнению с копированием значения практически нет. То же самое делать для float и double. Однако уже для vector<string> v всегда применять for (const string& s: v). Для сложных пользовательских типов (class, struct) — тем более.
Почти. Не "всегда", а если нет причины делать иначе.
L>2. Или же вообще не усложнять себе жизнь, а для этого в любых случаях всегда применять for (auto t: v).
Нет. Копирования могут что-то сломать и вообще они дороги.
Здравствуйте, LaptevVV, Вы писали:
LVV>Считай 1 элемент цикла — параметром (как в функциях). И поступай так же, как с параметрами.
А если цикл будет внутри функции, которая принимает константную ссылку на вектор? Например, такая функция:
void PrintVector(const vector<string>& v) {
for (... s: v) {
cout << s << " ";
}
}
На месте многоточия (…) должно стоять const string& — поскольку 1 элемент цикла надо считать параметром, как в функциях? Или здесь должно быть просто string?
LVV>>Считай 1 элемент цикла — параметром (как в функциях). И поступай так же, как с параметрами. L>А если цикл будет внутри функции, которая принимает константную ссылку на вектор? Например, такая функция: L>
L>На месте многоточия (…) должно стоять const string& — поскольку 1 элемент цикла надо считать параметром, как в функциях? Или здесь должно быть просто string?
Ну, сам for можно считать вызовом функции, у которой параметром является первый элемент.
В данном конкретном случае вполне подойдет const string &s
Хочешь быть счастливым — будь им!
Без булдырабыз!!!
L>В цикле for вместо int часто указывают auto, тогда компилятор сам определяет тип переменной t. Но оказывается, вместо int можно также указать ссылку int& — у кого-то я видел такое, и в моем примере это нормально работает. Для надежности я указал const int&, и у меня в данном примере это тоже нормально работает. L>Все же, как лучше делать, в каких случаях и почему?
Я привык так писать:
for( const auto& it: test)
{
cout << it << " ";
}
Иногда кстати встречается еще более интересный вариант
Здравствуйте, Lanjeron32, Вы писали:
L>2. Или же вообще не усложнять себе жизнь, а для этого в любых случаях всегда применять for (auto t: v).
Как раз из этих соображений — чтобы не усложнять себе жизнь — в качестве универсального варианта лучше бы выбрать "for (const auto& t : v)". Ибо урон от нежелательного копирования может быть существеенно более ощутимый, чем от лишней косвенности. С которой современые оптимизаторы справляются без особого труда.
Только в этом случае "const" уже является лишним и будет только приводить к ошибкам компиляции. Вообще "auto&&" и "const auto&&" — это две большие разницы. Первый вариант — это так называемая forwarding reference (или, как ее назвали раньше "universal reference"), тогда как второй — просто константная rvalue ссылка.
Здравствуйте, Lanjeron32, Вы писали:
L>Мне тоже несколько раз уже встречался такой вариант. Это для передачи по константной ссылке rvalue, правильно? L>UPD: а, вижу, rg45 на этот вопрос уже ответил.
Чуть более развернуто: конструкция "auto&&" является автоподстраиваемой и может развернуться как в константную lvalue ссылку, так и в неконстантную — в зависимости от константности самого контейнера. Вот только полезность такой гибкости конкретно при пременении внутри циклов range for мне кажется сомнительной. Просто не могу придумать практически полезный пример, когда бы мы не знали, какой контейнер мы обрабатываем, константый или неконстантный.
[Upd]
Конструкция "auto&&" могла бы разворачиваться и в rvalue reference, если бы операции разыменования итераторов страндартных контейнеров возвращали либо по значению, либо rvalue ссылки. Но такого нет в стандартной библиотеке — все эти операции возвращают, by design, только lvalue ссылки — либо константные, либо некостантые. Соответственно и конструкция "auto&&" может развернуться только в lvalue ссылку, либо константную, либо неконстантную. Происходит это примерно так: допустим, операция разыменования неконстантного итератора возвращает неконстантную ссылку "int &". Тогда конструкция "auto &&" сначала разворачивается в промежуточную конструкцию вида: "int & &&". Как бы "rvalue ссылка на lvalue ссылку". Затем эта кострукция, благодаря механизму reference collapsing, появившемуся в C++11, приобретает свою финальную форму: "int&".
Здравствуйте, rg45, Вы писали:
R>Чуть более развернуто: конструкция "auto&&" является автоподстраиваемой и может развернуться как в константную lvalue ссылку, так и в неконстантную — в зависимости от константности самого контейнера. Вот только полезность такой гибкости конкретно при пременении внутри циклов range for мне кажется сомнительной. Просто не могу придумать практически полезный пример, когда бы мы не знали, какой контейнер мы обрабатываем, константый или неконстантный.
Для меня передача rvalue по ссылке, семантика move, все это пока еще довольно сложно. Мне бы сейчас разобраться с тем простым примером, где в функцию передается vector<string>, а в теле функции есть цикл for. В учебном примере я видел, сделано так:
void PrintVector(const vector<string>& v) {
for (string s: v) {
cout << s << " ";
}
}
Как я понял из предыдущих ответов, в цикле for лучше вместо string поставить const string&. Но вопросы у меня еще остаются. Вектор v в функцию передается по константной ссылке, и в теле функции доступен по константной ссылке. Значит, каждый элемент s этого вектора в теле функции тоже доступен по константной ссылке, правильно? Тогда почему автор все же использует string? (между прочим, автор — ведущий разработчик Яндекса. Фамилию называть не буду, но очень сильный специалист).
Здравствуйте, Lanjeron32, Вы писали:
L>Как я понял из предыдущих ответов, в цикле for лучше вместо string поставить const string&. Но вопросы у меня еще остаются. Вектор v в функцию передается по константной ссылке, и в теле функции доступен по константной ссылке. Значит, каждый элемент s этого вектора в теле функции тоже доступен по константной ссылке, правильно?
Все верно.
L>Тогда почему автор все же использует string? (между прочим, автор — ведущий разработчик Яндекса. Фамилию называть не буду, но очень сильный специалист).
Как я полагаю, просто для упрощения текста примеров. Для учебных примеров такие вопросы оптимизации не являются существенными. Тем более с учетом того, что многие реализации std::string используют технологию "copy on write". Хотя эффект получается прямо противоположный ожидаемому — вместо упрощения у читателей появляются вопросы, вполне закономерные.
Здравствуйте, rg45, Вы писали:
R>Конструкция "auto&&" могла бы разворачиваться и в rvalue reference, если бы операции разыменования итераторов страндартных контейнеров возвращали либо по значению, либо rvalue ссылки. Но такого нет в стандартной библиотеке — все эти операции возвращают, by design, только lvalue ссылки — либо константные, либо некостантые.
Есть. Есть распространённый пример: std::vector<bool> для которого ссылки не компилируются:
template<typename Range, typename Value>
void set_to(Range& range, const Value& value)
{
for (auto& x : range)
{
x = value;
}
}
Здравствуйте, Lanjeron32, Вы писали:
L>Привет всем, у меня такой вопрос. Есть обычный vector<int> и нужно просто вывести на консоль его элементы. L>Все же, как лучше делать, в каких случаях и почему?
Советую сначала изучать ассемблер, как минимум чтобы представлять что происходит когда исполняется код на C++. И только потом уже заморачиваться на последние стандарты C++, если захочешь.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)