Решение алгоритма:
std::generate(numbers.begin(), numbers.end(), rand);
Решение для цикла на основе диапазона:
for (int& x : numbers) x = rand();
Зачем мне использовать более подробные std::generate
циклы for на основе диапазона в C ++ 11?
begin()
иend()
?range
функция в своем наборе инструментов. (iefor(auto& x : range(first, last))
)boost::generate(numbers, rand); // ♪
Ответы:
Первая версия
std::generate(numbers.begin(), numbers.end(), rand);
сообщает нам, что вы хотите создать последовательность значений.
Во второй версии читателю придется разобраться в этом самому.
Экономия на печатании обычно неоптимальна, так как чаще всего теряется время чтения. Большая часть кода читается гораздо больше, чем набирается.
источник
numbers
дважды без причины. Следовательно:boost::range::generate(numbers, rand);
. Нет причин, по которым у вас не может быть и более короткого, и более разборчивого кода в хорошо построенной библиотеке.std::generate(number.begin(), numbers.begin()+3, rand)
, не так ли? Так что, я думаю,number
иногда бывает полезно указать дважды.std::generate()
, вы можете вместо этогоstd::generate(slice(number.begin(), 3), rand)
или даже лучше использовать гипотетический синтаксис нарезки диапазона, например,std::generate(number[0:3], rand)
который удаляет повторение, но приnumber
этом позволяет гибкую спецификацию части диапазона. Сделать обратное, начиная с трех аргументовstd::generate()
, более утомительно.Независимо от того, основан ли цикл for на диапазоне или нет, это вообще не имеет значения, он только упрощает код внутри круглых скобок. Алгоритмы более ясны, поскольку они показывают намерение .
источник
Лично мое первоначальное прочтение:
std::generate(numbers.begin(), numbers.end(), rand);
это «мы назначаем все в диапазоне. Диапазон равен
numbers
. Присвоенные значения случайны».Мое первое прочтение:
for (int& x : numbers) x = rand();
это «мы делаем что-то для всего в диапазоне. Диапазон есть
numbers
. Что мы делаем, так это присваиваем случайное значение».Они чертовски похожи, но не идентичны. Одна вероятная причина, по которой я мог бы спровоцировать первое чтение, состоит в том, что я думаю, что наиболее важным фактом в этом коде является то, что он присваивает диапазон. Итак, вот ваше «зачем мне…». Я использую,
generate
потому что в C ++std::generate
означает «присвоение диапазона». Как, кстати,std::copy
, разница между ними заключается в том, что вы назначаете.Однако есть смешивающие факторы. Циклы for, основанные на диапазоне, по своей сути имеют более прямой способ выражения диапазона
numbers
, чем алгоритмы на основе итераторов. Вот почему люди работают с библиотеками алгоритмов на основе диапазонов:boost::range::generate(numbers, rand);
выглядит лучше, чемstd::generate
версия.В отличие от этого,
int&
в вашем цикле for на основе диапазона есть морщинка. Что, если тип значения диапазона не является типом значенияint
, тогда мы делаем здесь что-то раздражающе тонкое, что зависит от его преобразованияint&
, в то время какgenerate
код зависит только от того, что возврат можетrand
быть назначен элементу. Даже если тип значения естьint
, я все равно могу остановиться, чтобы подумать, так оно или нет. Следовательноauto
, это откладывает размышления о типах до тех пор, пока я не увижу, что назначается -auto &x
я говорю: «возьмите ссылку на элемент диапазона, какой бы тип он ни имел». Еще в C ++ 03, алгоритмы (потому что они шаблоны функций) были способом скрыть точные типы, теперь они путь.Я думаю, что всегда было так, что простейшие алгоритмы имели лишь незначительное преимущество перед эквивалентными циклами. Циклы for на основе диапазона улучшают циклы (в первую очередь за счет удаления большей части шаблонного кода, хотя это еще не все). Таким образом, поля становятся более узкими, и, возможно, вы передумаете в некоторых конкретных случаях. Но разница в стилях все же есть.
источник
operator int&()
? :)int&
на,SomeClass&
и теперь вам нужно беспокоиться о том, что операторы преобразования и конструкторы с одним параметром не отмеченыexplicit
.operator int&()
иoperator int const &() const
, но опять же может работать с перегрузкойoperator int() const
иoperator=(int)
.На мой взгляд, Эффективный пункт 43 STL: «Предпочитайте вызовы алгоритмов рукописным циклам». по-прежнему хороший совет.
Обычно я пишу функции-обертки, чтобы избавиться от
begin()
/end()
hell. Если вы это сделаете, ваш пример будет выглядеть так:Я считаю, что он превосходит цикл for на основе диапазона как в передаче намерения, так и в удобочитаемости.
Сказав это, я должен признать, что в C ++ 98 некоторые вызовы алгоритмов STL приводили к непроизносимому коду, и последующее «Предпочитать вызовы алгоритмов рукописным циклам» не казалось хорошей идеей. К счастью, лямбды это изменили.
Рассмотрим следующий пример из Herb Sutter: Lambdas, Lambdas Everywhere .
Задача: найти первый элемент в v, то есть
> x
и< y
.Без лямбд:
auto i = find_if( v.begin(), v.end(), bind( logical_and<bool>(), bind(greater<int>(), _1, x), bind(less<int>(), _1, y) ) );
С лямбдой
auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );
источник
На мой взгляд, ручной цикл, хотя и может уменьшить многословие, не читается:
for (int& x : numbers) x = rand();
Я бы не использовал этот цикл для инициализации 1 диапазона, определенного числами , потому что, когда я смотрю на него, мне кажется, что он выполняет итерацию по диапазону чисел, но на самом деле это не так (по сути), т.е. вместо чтение из диапазона, запись в диапазон.
Намерение становится намного яснее, когда вы используете
std::generate
.1. Инициализировать в данном контексте означает придать значимое значение элементам контейнера.
источник
std::generate
, что можно предположить о программисте на C ++ (если они не знакомы, они найдут его, результат тот же).&
символ?). Преимущество алгоритмов в том, что они показывают намерение, а с циклами вы должны это сделать. Если в реализации цикла есть ошибка, неясно, является ли это ошибкой или она была преднамеренной.std::generate
, просто взглянув, можно сказать, что что- то генерируется этой функцией; на что то что-то отвечает третий аргумент функции. Я думаю, это намного лучше.Есть некоторые вещи, которые нельзя делать (просто) с циклами на основе диапазона, которые могут использовать алгоритмы, принимающие итераторы в качестве входных данных. Например с
std::generate
:Заполните контейнер до
limit
(исключено,limit
является действующим итераторомnumbers
) переменными из одного распределения, а остальные - переменными из другого распределения.std::generate(numbers.begin(), limit, rand1); std::generate(limit, numbers.end(), rand2);
Алгоритмы на основе итераторов позволяют лучше контролировать диапазон, в котором вы работаете.
источник
В конкретном случае
std::generate
я согласен с предыдущими ответами по проблеме читабельности / намерения. std :: generate мне кажется более понятной версией. Но признаю, что это в некотором роде дело вкуса.Тем не менее, у меня есть еще одна причина не отказываться от std :: algorithm - есть определенные алгоритмы, которые специализируются на некоторых типах данных.
Простейшим примером будет
std::fill
. Общая версия реализована как цикл for в указанном диапазоне, и эта версия будет использоваться при создании экземпляра шаблона. Но не всегда. Например, если вы предоставите ему диапазон, который являетсяstd::vector<int>
- часто он действительно будет вызыватьmemset
под капотом, давая гораздо более быстрый и лучший код.Итак, я пытаюсь разыграть здесь карту эффективности.
Ваш рукописный цикл может быть таким же быстрым, как версия std :: algorithm, но вряд ли быстрее. Более того, std :: algorithm может быть специализирован для определенных контейнеров и типов, и это делается под чистым интерфейсом STL.
источник
Я бы ответил, может быть, и нет. Если мы говорим о C ++ 11, то возможно (скорее нет). Например
std::for_each
, очень неприятно использовать даже с лямбдами:std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x) { // do stuff with x });
Но намного лучше использовать диапазон на основе:
for (auto& x : c) { // do stuff with x }
С другой стороны, если мы говорим о C ++ 1y, то я бы сказал, что нет, алгоритмы не будут устаревать на основе диапазона для. В стандартном комитете C ++ есть исследовательская группа, которая работает над предложением о добавлении диапазонов в C ++, а также ведется работа над полиморфными лямбдами. Диапазоны избавят от необходимости использовать пару итераторов, а полиморфная лямбда позволит вам не указывать точный тип аргумента лямбда. Это означает, что это
std::for_each
можно использовать так (не воспринимайте это как твердый факт, это просто то, как сегодня выглядят сны):std::for_each(c.range(), [](x) { // do stuff with x });
источник
[]
с лямбда- выражением вы указываете нулевой захват? То есть, по сравнению с простым написанием тела цикла, вы изолировали кусок кода от контекста поиска переменной, в котором он лексически появляется. Изоляция обычно полезна для читателя, о чем меньше думать во время чтения.for_each
все еще бессмысленно даже при использовании с лямбдой. foreach + захват лямбда-выражения в настоящее время является подробным способом написания цикла for на основе диапазона, и он становится немного менее подробным, но все же более подробным, чем цикл. Не то чтобы я думаю, что вам следует защищатьсяfor_each
, конечно, но еще до того, как увидел ваш ответ, я подумал, что если бы спрашивающий хотел обойти алгоритмы, он мог бы выбратьfor_each
самую мягкую из всех возможных целей ;-)for_each
, но у него есть одно крошечное преимущество перед основанным на диапазоне for - вы можете сделать его параллельным более легко, просто добавив к нему префикс parallel_, чтобы превратить его вparallel_for_each
(если вы используете PPL и предполагаете, что это потокобезопасно) . :-Dstd::algorithm
s скрыта за их интерфейсом и может быть произвольно сложной (или произвольно оптимизированной).Следует отметить, что алгоритм выражает то, что сделано, а не как.
Цикл на основе диапазона включает в себя способ выполнения: начать с первого, применить и перейти к следующему элементу до конца. Даже простой алгоритм мог бы действовать по-другому (по крайней мере, некоторые перегрузки для конкретных контейнеров, даже не думая об ужасном векторе), и, по крайней мере, способ, которым это делается, не является делом писателя.
Для меня это большая разница, инкапсулируйте как можно больше, и это оправдывает предложение, когда вы можете использовать алгоритмы.
источник
Цикл for на основе диапазона - это именно то, что вам нужно. Пока конечно стандарт не изменится.
Алгоритм - это функция. Функция, которая предъявляет некоторые требования к своим параметрам. Требования сформулированы в стандарте, что позволяет, например, реализовать реализацию, которая использует все доступные потоки выполнения и автоматически ускоряет работу.
источник