Является ли цикл " for " на основе диапазона устаревшим для многих простых алгоритмов?



алгоритм решения:



std::generate(numbers.begin(), numbers.end(), rand);


Range-based for-loop solution:



for (int& x : numbers) x = rand();


почему я хочу использовать более подробный std::generate на основе диапазона для циклов в C++11?

606   10  

10 ответов:

первый вариант

std::generate(numbers.begin(), numbers.end(), rand);

говорит нам, что вы хотите создать последовательность значений.

во второй версии читатель должен будет выяснить это сам.

экономия на наборе текста обычно неоптимальна, так как она чаще всего теряется во время чтения. Самый код читается намного больше, чем набирается.

независимо от того, является ли цикл for диапазоном или нет, это не имеет никакого значения, он только упрощает код внутри скобки. Алгоритмы яснее в том, что они показывают намерение.

лично мое первоначальное чтение:

std::generate(numbers.begin(), numbers.end(), rand);

это "мы назначаем все в диапазоне. Диапазон составляет numbers. Присвоенные значения являются случайными".

мое первоначальное чтение:

for (int& x : numbers) x = rand();

это "мы делаем что-то все в ассортименте. Диапазон составляет numbers. То, что мы делаем, это назначить случайное значение."

они чертовски похожи, но не идентичны. Одна из вероятных причин, по которой я могу захотеть спровоцировать первое чтение, это потому что я думаю, что самый важный факт об этом коде заключается в том, что он присваивает диапазон. Итак, вот ваш "почему я хочу...". Я использую generate потому что в C++ std::generate означает "назначение диапазона". Как кстати делает std::copy, разница между этими двумя является то, что вы назначаете от.

есть несколько факторов, хотя. Range-based for loops имеют по своей сути более прямой способ выражения того, что диапазон numbers, чем итератора алгоритмы делать. Вот почему люди работают на библиотеки алгоритмов на основе диапазона:boost::range::generate(numbers, rand); выглядит лучше, чем std::generate версия.

а то int& в вашем диапазоне на основе петли является морщина. Что делать, если тип значения диапазона не int, тогда мы делаем что-то раздражающе тонкое здесь, что зависит от того, что он конвертируется в int&, а generate код зависит только от возврата от rand присваивается элементу. Даже если тип значения int, Я все еще могу перестать думать о так это или нет. Отсюда auto, который откладывает думать о типах, пока я не увижу, что назначается - с auto &x я говорю: "возьмите ссылку на элемент диапазона, независимо от типа, который может иметь". Еще в C++03 алгоритмы (потому что это шаблоны функций) были the способ скрыть точные типы, теперь они a путь.

я думаю, что это всегда было так, что самые простые алгоритмы имеют только предельное преимущество над эквивалентными циклами. Диапазон на основе петель улучшает петли (в первую очередь, удаляя большую часть шаблона, хотя есть немного больше, чем это). Таким образом, поля затягиваются, и, возможно, вы передумаете в некоторых конкретных случаях. Но есть еще разница в стиле там.

на мой взгляд, эффективный STL пункт 43: "предпочитаю вызовы алгоритмов рукописным циклам.- и все же это хороший совет.

Я обычно пишу функции обертки, чтобы избавиться от begin()/end() ад. Если вы это сделаете, ваш пример будет выглядеть так:

my_util::generate(numbers, rand);

Я считаю, что он бьет диапазон на основе цикла как в общение намерением и в читабельности.


сказав это, я должен признать, что в C++98 некоторые вызовы алгоритма STL давали невыразимый код, и следующие "предпочтительные вызовы алгоритма для рукописных циклов" не казались хорошей идеей. К счастью, лямбды изменили это.

рассмотрим следующий пример из Херб Саттер: Лямбда, Лямбда Везде.

задачи: найти первый элемент в 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:: - есть определенные алгоритмы, которые специализируются на некоторых типах данных.

самый простой пример std::fill. Общая версия реализована в виде цикла for предоставляемый ассортимент, и эта версия будет использоваться при создании шаблона. Но не всегда. Например, если вы предоставите ему диапазон, который является std::vector<int> - часто это будет на самом деле вызов memset под капотом, давая гораздо быстрее и лучше код.

поэтому я пытаюсь разыграть здесь карту эффективности.

ваш рукописный цикл может быть таким же быстрым, как версия алгоритма std::, но вряд ли он может быть быстрее. И более того, алгоритм std::может быть специализирован для конкретного контейнеры и типы, и это делается под чистым интерфейсом 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-loop-это именно то. Пока, конечно, стандарт не будет изменен.

алгоритм-это функция. Функция, которая предъявляет некоторые требования к ее параметрам. Требования сформулированы в стандарте, чтобы обеспечить, например, реализацию, которая использует все доступные потоки выполнения и ускорит вас автоматически.

Comments

    Ничего не найдено.