Решение алгоритма:
std::generate(numbers.begin(), numbers.end(), rand);
Решение для цикла на основе диапазона:
for (int& x : numbers) x = rand();
Зачем мне использовать более подробные std::generateциклы for на основе диапазона в C ++ 11?
Решение алгоритма:
std::generate(numbers.begin(), numbers.end(), rand);
Решение для цикла на основе диапазона:
for (int& x : numbers) x = rand();
Зачем мне использовать более подробные std::generateциклы for на основе диапазона в C ++ 11?
begin()и end()?
rangeфункция в своем наборе инструментов. (ie for(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. Если вы это сделаете, ваш пример будет выглядеть так:
my_util::generate(numbers, rand);
Я считаю, что он превосходит цикл 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 и предполагаете, что это потокобезопасно) . :-D
std::algorithms скрыта за их интерфейсом и может быть произвольно сложной (или произвольно оптимизированной).
Следует отметить, что алгоритм выражает то, что сделано, а не как.
Цикл на основе диапазона включает в себя способ выполнения: начать с первого, применить и перейти к следующему элементу до конца. Даже простой алгоритм мог бы действовать по-другому (по крайней мере, некоторые перегрузки для конкретных контейнеров, даже не думая об ужасном векторе), и, по крайней мере, способ, которым это делается, не является делом писателя.
Для меня это большая разница, инкапсулируйте как можно больше, и это оправдывает предложение, когда вы можете использовать алгоритмы.
Цикл for на основе диапазона - это именно то, что вам нужно. Пока конечно стандарт не изменится.
Алгоритм - это функция. Функция, которая предъявляет некоторые требования к своим параметрам. Требования сформулированы в стандарте, что позволяет, например, реализовать реализацию, которая использует все доступные потоки выполнения и автоматически ускоряет работу.