Подтвердить что ты не робот

Должен ли я использовать std:: for_each?

Я всегда стараюсь больше узнать о языках, которые я использую (разные стили, рамки, шаблоны и т.д.). Я заметил, что никогда не использовал std::for_each, поэтому я подумал, что, возможно, мне следует начать. Цель в таких случаях - расширить мой разум и не, чтобы в какой-то мере улучшить код (читаемость, выразительность, компактность и т.д.).

Таким образом, имея в виду этот контекст, рекомендуется использовать std::for_each для простых задач, например, для печати вектора:

for_each(v.begin(), v.end(), [](int n) { cout << n << endl; }

([](int n) является лямбда-функцией). Вместо:

for(int i=0; i<v.size(); i++) { cout << v[i] << endl; }

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

4b9b3361

Ответ 1

Преимущество использования std::for_each вместо старой школы for цикла (или даже цикла с новичком в С++ 0x range - for): вы можете посмотреть первое слово инструкции, и вы точно знать, что делает инструкция.

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

В цикле for вы должны прочитать весь цикл цикла, чтобы узнать, что он делает. Он может иметь в нем continue, break или return заявления, которые изменяют поток управления. У него могут быть инструкции, которые изменяют итератор или переменную индекса. Невозможно узнать, не исследуя весь цикл.

Херб Саттер обсудил преимущества использования алгоритмов и лямбда-выражений в недавней презентации для Северо-Западной группы пользователей С++.

Обратите внимание, что вы можете использовать алгоритм std::copy здесь, если хотите:

std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, "\n"));

Ответ 2

Это зависит.

Сила for_each заключается в том, что вы можете использовать ее с любым контейнером, итераторы которого удовлетворяют концепции ввода итератора и, как таковой, могут использоваться в любом контейнере. Это увеличивает ремонтопригодность таким образом, что вы можете просто заменить контейнер и не нужно ничего менять. То же самое не выполняется для цикла над вектором size. Единственными другими контейнерами, которые вы могли бы поменять, без необходимости менять цикл, будет другой случайный доступ.

Теперь, если вы сами выберете версию итератора, типичная версия будет выглядеть так:

// substitute 'container' with a container of your choice
for(std::container<T>::iterator it = c.begin(); it != c.end(); ++it){
  // ....
}

Скорее длинный, а? С++ 0x избавляет нас от этой длины с ключевым словом auto:

for(auto it = c.begin(); it != c.end(); ++it){
  // ....
}

Уже приятнее, но все же не идеально. Вы вызываете end на каждой итерации, и это можно сделать лучше:

for(auto it = c.begin(), ite = c.end(); it != ite; ++it){
  // ....
}

Теперь выглядит хорошо. Тем не менее, дольше, чем эквивалентная версия for_each:

std::for_each(c.begin(), c.end(), [&](T& item){
  // ...
});

С "эквивалентным", слегка субъективным, поскольку T в списке параметров лямбда может быть некоторым многословным типом типа my_type<int>::nested_type. Хотя, можно обойти это. Честно говоря, я до сих пор не понимаю, почему лямбдам не разрешалось быть полиморфными с типом дедукции...


Теперь еще одна вещь, которую следует учитывать, состоит в том, что for_each, само имя, уже выражает намерение. В нем говорится, что никакие элементы в последовательности не будут пропущены, что может иметь место с вашим обычным циклом.

Это приводит меня к другой точке: поскольку for_each предназначен для выполнения всей последовательности и применения операции для каждого элемента в контейнере, он не предназначен для обработки ранних return или break в целом. continue можно моделировать с помощью инструкции return от лямбда/функтора.

Итак, используйте for_each, где вы действительно хотите применить операцию для каждого элемента в коллекции.

На стороне примечание for_each может быть просто "устаревшим" с С++ 0x благодаря удивительным диапазонам for-loops (также называемым петлями foreach):

for(auto& item : container){
  // ...
}

Какой путь короче (yay) и разрешает все три варианта:

  • возврат рано (даже с возвращаемым значением!)
  • выход из цикла и
  • пропуская некоторые элементы.

Ответ 3

Обычно я рекомендую использовать std::for_each. Ваш пример цикла не работает для контейнеров без случайного доступа. Вы можете написать тот же цикл, используя итераторы, но обычно это боль из-за записи std::SomeContainerName<SomeReallyLongUserType>::const_iterator в качестве типа переменной итерации. std::for_each изолирует вас от этого, а также автоматически отменяет вызов end.

Ответ 4

IMHO, вы должны попробовать эти новые функции в тестовом коде.

В производственном коде вы должны попробовать функции, с которыми вам комфортно. (т.е. если вы чувствуете себя комфортно с for_each, вы можете использовать его.)

Ответ 5

for_each является наиболее общим из алгоритмов, которые перебирают последовательность и, следовательно, наименее выразительны. Если цель итерации может быть выражена через transform, accumulate, copy, я считаю, что лучше использовать конкретный алгоритм, а не общий for_each.

С новым диапазоном С++ 0x (поддерживается в gcc 4.6.0, попробуйте это!), for_each может даже потерять свою нишу в качестве наиболее общего способа применения функции к последовательности.

Ответ 6

Вы можете использовать for обзор цикла С++ 11

Например:

 T arr[5];
 for (T & x : arr) //use reference if you want write data
 {
 //something stuff...
 }

Где T - любой тип, который вы хотите.

Он работает для каждого контейнера в STL и классических массивах.

Ответ 7

Ну... это работает, но для печати вектора (или содержимого других типов контейнеров) я предпочитаю это:

std::copy(v.begin(), v.end(), std::ostream_iterator< int >( std::cout, " " ) );

Ответ 8

Boost.Range упрощает использование стандартных алгоритмов. Для вашего примера вы можете написать:

boost::for_each(v, [](int n) { cout << n << endl; });

(или boost::copy с итератором ostream, как предложено в других ответах).

Ответ 9

Обратите внимание, что пример "традиционный" не работает:

for(int i=0; i<v.size(); i++) { cout << v[i] << endl; }

Это предполагает, что int всегда может представлять индекс каждого значения в векторе. На самом деле есть два пути: это может пойти не так.

Во-первых, int может иметь более низкий ранг, чем std::vector<T>::size_type. На 32-битной машине int обычно имеют ширину в 32 бита, но v.size() почти наверняка будет шириной 64 бит. Если вам удастся нанести 2 ^ 32 элемента в вектор, ваш индекс никогда не достигнет конца.

Вторая проблема заключается в том, что вы сравниваете знаковое значение (int) с неподписанным значением (std::vector<T>::size_type). Поэтому, даже если они имеют одинаковый ранг, когда размер превышает максимальное целочисленное значение, тогда индекс будет переполняться и запускать поведение undefined.

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

Итак, да, for_each (или любой подходящий <algorithm>) лучше, потому что он избегает этого пагубного злоупотребления int s. Вы также можете использовать цикл, основанный на диапазоне, или петлю на основе итератора с авто.

Дополнительным преимуществом использования <algorithm> или итераторов, а не индексов является то, что он дает вам больше гибкости для изменения типов контейнеров в будущем без реорганизации всего кода, который его использует.