Решение алгоритма:
std::generate(numbers.begin(), numbers.end(), rand);
Решение, основанное на контурах:
for (int& x : numbers) x = rand();
Почему я хотел бы использовать более подробные std::generate
для циклов for-loop в С++ 11?
Решение алгоритма:
std::generate(numbers.begin(), numbers.end(), rand);
Решение, основанное на контурах:
for (int& x : numbers) x = rand();
Почему я хотел бы использовать более подробные std::generate
для циклов for-loop в С++ 11?
Первая версия
std::generate(numbers.begin(), numbers.end(), rand);
сообщает нам, что вы хотите сгенерировать последовательность значений.
Во второй версии читателю придется самому понять это.
Сохранение при вводе обычно субоптимально, поскольку оно чаще всего теряется во время чтения. Большая часть кода читается намного больше, чем набирается.
Независимо от того, основан ли цикл for на основе диапазона или нет, это не делает различий вообще, оно только упрощает код внутри скобки. Алгоритмы более ясны в том смысле, что они показывают намерение.
Лично мое первоначальное чтение:
std::generate(numbers.begin(), numbers.end(), rand);
: "мы назначаем все в диапазоне. Диапазон numbers
. Значения, назначенные, являются случайными".
Мое начальное чтение:
for (int& x : numbers) x = rand();
- "мы делаем что-то во всем в диапазоне. Диапазон numbers
. Мы делаем назначение случайного значения".
Это довольно штопают похожие, но не идентичные. Одна из правдоподобных причин, по которой я мог бы спровоцировать первое чтение, состоит в том, что я думаю, что самый важный факт в этом коде состоит в том, что он присваивает диапазону. Так вот, "почему я хочу...". Я использую generate
, потому что в С++ std::generate
означает "назначение диапазона". Поскольку btw делает std::copy
, разница между этими двумя - это то, что вы назначаете.
numbers
, чем алгоритмы на основе итератора. Вот почему люди работают над библиотеками алгоритмов на основе диапазонов: boost::range::generate(numbers, rand);
выглядит лучше, чем версия std::generate
.
В отличие от этого, int&
в вашем цикле, основанном на диапазоне, является морщиной. Что, если тип значения диапазона не int
, то мы делаем что-то досадно тонкое здесь, которое зависит от его конвертируемого в int&
, тогда как код generate
зависит только от возврата из rand
назначаемый элементу. Даже если тип значения int
, я все равно могу перестать думать о том, есть он или нет. Следовательно, auto
, который отвлекает внимание на типы до тех пор, пока не увижу, что назначается - с auto &x
я говорю: "Возьмите ссылку на элемент диапазона, какой бы тип он ни имел". Назад в С++ 03, алгоритмы (потому что они являются шаблонами функций) были способом скрыть точные типы, теперь это способ.
Я думаю, что всегда было так, что простейшие алгоритмы имеют лишь незначительное преимущество над эквивалентными циклами. Диапазоны для петель улучшают контуры (в основном, удаляя большую часть шаблона, хотя для них немного больше). Таким образом, маржи становятся более жесткими и, возможно, вы передумаете в некоторых конкретных случаях. Но там есть еще различие стиля.
На мой взгляд, эффективный STL. Пункт 43: "Предпочитает алгоритм вызова ручных петель". по-прежнему является хорошим советом.
Я обычно пишу обертки, чтобы избавиться от атак begin()
/end()
. Если вы это сделаете, ваш пример будет выглядеть следующим образом:
my_util::generate(numbers, rand);
Я считаю, что это превосходит диапазон, основанный на цикле, как для передачи намерения, так и для читаемости.
Сказав это, я должен признать, что в С++ 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; } );
На мой взгляд, ручная петля, хотя и может уменьшить многословие, не имеет readabitly:
for (int& x : numbers) x = rand();
Я бы не использовал этот цикл для инициализации диапазона 1 определенного числами, потому что, когда я смотрю на него, мне кажется, что он выполняет итерацию по целому ряду чисел, но на самом деле это (по существу), т.е. вместо чтения из диапазона, он записывает в диапазон.
Умывка намного понятнее, если вы используете std::generate
.
1. инициализация в этом контексте означает предоставление значимого значения элементам контейнера.
Есть некоторые вещи, которые вы не можете сделать (просто) с циклами на основе диапазона, которые могут использовать алгоритмы, которые принимают итераторы в качестве входных данных.
Например, с помощью 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
. Общая версия реализована как циклический цикл по предоставленному диапазону, и эта версия будет использоваться при создании экземпляра шаблона. Но не всегда. Например. если вы предоставите ему диапазон std::vector<int>
- часто он на самом деле называет memset
под капотом, что дает гораздо более быстрый и лучший код.
Итак, я пытаюсь сыграть карту эффективности здесь.
Ваша рукописная петля может быть такой же быстрой, как версия std:: algorithm, но она вряд ли может быть быстрее. И более того, std:: algorithm может быть специализирован для конкретных контейнеров и типов, и это делается под чистым интерфейсом STL.
Мой ответ может быть и нет. Если мы говорим о С++ 11, то, может быть, (больше не нравится). Например, std::for_each
действительно раздражает использование даже с lambdas:
std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
// do stuff with x
});
Но использование диапазона на основе намного лучше:
for (auto& x : c)
{
// do stuff with x
}
С другой стороны, если мы говорим о С++ 1y, то я бы сказал, что нет, алгоритмы не будут устаревать по диапазону. В стандартном комитете С++ есть исследовательская группа, которая работает над предложением о добавлении диапазонов в С++, а также выполняется работа над полиморфными лямбдами. Диапазоны устраняют необходимость использования пары итераторов, а полиморфная лямбда позволит вам не указывать тип точного аргумента лямбда. Это означает, что std::for_each
можно использовать как это (не считайте это сложным фактом, это то, что сегодня выглядит сны):
std::for_each(c.range(), [](x)
{
// do stuff with x
});
Следует заметить, что алгоритм выражает то, что делается, а не как.
Цикл, основанный на диапазонах, включает в себя то, как все делается: начните с первого, примените и перейдите к следующему элементу до конца. Даже простой алгоритм может делать что-то по-другому (по крайней мере, некоторые перегрузки для конкретных контейнеров, даже не думая о ужасном векторе), и, по крайней мере, так, как это делается, это не бизнес писателя.
Для меня большая часть разницы, инкапсулируйте столько, сколько сможете, и это оправдывает предложение, когда вы можете, используйте алгоритмы.
Диапазон для цикла - это просто. До тех пор, пока, конечно, не будет изменен стандарт.
Алгоритм - это функция. Функция, которая предъявляет некоторые требования к ее параметрам. Требования сформулированы в стандарте, чтобы, например, реализовать реализацию, которая использует все доступные потоки выполнения и автоматически ускорит процесс.