Предпочитаете алгоритмы рукописным циклам?


10

Что из следующего вам кажется более читабельным? Рукописный цикл:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

Или вызов алгоритма:

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

Интересно, std::for_eachдействительно ли это того стоит, учитывая, что такой простой пример уже требует так много кода.

Что вы думаете об этом?


2
Для C ++ ответ довольно очевиден. Но в других языках оба они примерно равны (например, Python: map(bar.process, vec)хотя карта для побочных эффектов не рекомендуется, и списочные выражения / выражения генератора рекомендуются над картой).

5
И затем есть также BOOST_FOREACH...
Бенджамин Баннье

Значит, пункт в действующем STL вас не убедил? stackoverflow.com/questions/135129/…
Работа

Ответы:


18

Есть причина, по которой были введены лямбды, и это потому, что даже Стандартный комитет признает, что вторая форма - отстой. Используйте первую форму, пока не получите поддержку C ++ 0x и лямбда-выражений.


2
Те же рассуждения могут быть использованы для обоснования второй формы: Комитет по Стандартам признал, что он был настолько превосходным, что они ввели лямбды, чтобы сделать его еще лучше. :-p (+1 тем не менее)
Конрад Рудольф

Я не думаю, что второе - это плохо. Но это может быть лучше. Например, вы сознательно усложнили использование объекта Bar, не добавляя operator (). Используя это, он значительно упрощает точку вызова в цикле и делает код аккуратным.
Мартин Йорк,

Примечание: поддержка лямбды была добавлена ​​из-за двух основных жалоб. 1) Стандартный шаблон, необходимый для передачи параметров функторам. 2) Отсутствие кода в пункте вызова. Ваш код не удовлетворяет ни одному из них, поскольку вы просто вызываете метод. Итак 1) нет дополнительного кода для передачи параметров 2) код уже не находится в точке вызова, поэтому лямбда не улучшит удобство сопровождения кода. Так что да, лямбда - отличное дополнение. Это не хороший аргумент для них.
Мартин Йорк,

9

Всегда используйте вариант, который лучше всего описывает то, что вы собираетесь делать. Это

Для каждого элемента xв vecdo bar.process(x).

Теперь давайте рассмотрим примеры:

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

У нас for_eachтам тоже есть - ура . У нас есть [begin; end)диапазон, на котором мы хотим работать.

В принципе, алгоритм был намного более явным и поэтому предпочтительным по сравнению с любой рукописной реализацией. Но тогда ... Биндеры? Memfun? В основном C ++ interna о том, как заполучить функцию-член? Для моей задачи я не забочусь о них! Я также не хочу страдать от этого многословного, жуткого синтаксиса.

Теперь другая возможность:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

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

Следует признать, что это выглядит waay лучше , чем первое решение (по крайней мере, контур тела является гибким и достаточно четко), но все же, это на самом деле не что большой. Мы будем использовать этот, если у нас не было лучшей возможности, но, возможно, у нас есть ...

Лучше?

Теперь вернемся к for_each. Разве не было бы здорово буквально сказать for_each и быть гибким в операции, которая должна быть выполнена тоже? К счастью, начиная с C ++ 0x лямбды, мы

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

Теперь, когда мы нашли абстрактное, общее решение для многих связанных ситуаций, стоит отметить, что в данном конкретном случае существует абсолютный фаворит №1 :

foreach(const Foo& x, vec) bar.process(x);

Это действительно не может быть намного яснее, чем это. К счастью, в C ++ 0x get встроен похожий синтаксис !


2
Сказал, что «похожий синтаксис» for (const Foo& x : vec) bar.process(x);используется или используется, const auto&если хотите.
Джон Пурди

const auto&возможно? Не знал этого - отличная информация!
Дарио

8

Потому что это так нечитабельно?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}

4
Извините, но по какой-то причине там написано "подвергнута цензуре", где, по моему мнению, должен быть ваш код
Матеин Улхак

6
Ваш код дает предупреждение компилятору, потому что сравнивать intс и size_tопасно. Кроме того, индексация не работает, например, с каждым контейнером std::set.
fredoverflow

2
Этот код будет работать только для контейнеров с оператором индексации, которых мало. Это бессмысленно связывает алгоритм - что если карта <> подойдет лучше? Оригинал for loop и for_each не будет затронут.
JBRWilkinson

2
И сколько раз вы разрабатываете код для вектора, а затем решаете заменить его на карту? Как будто проектирование для этого было приоритетом языков.
Мартин Беккет

2
Итерации по коллекциям по индексу не рекомендуется, поскольку некоторые коллекции имеют низкую производительность при индексировании, тогда как все коллекции ведут себя хорошо при использовании итератора.
безрассудно

3

В общем, первая форма читаема практически любым, кто знает, что такое цикл for, независимо от того, какой у них фон.

Кроме того, в общем, второй не настолько удобен для чтения: достаточно просто понять, что делает for_each, но если вы никогда не видели, std::bind1st(std::mem_fun_refя могу представить, что это трудно понять.


2

На самом деле, даже с C ++ 0x, я не уверен, for_eachчто получит много любви.

for(Foo& foo: vec) { bar.process(foo); }

Это способ более читабельным.

Единственное, что мне не нравится в алгоритмах (в C ++) - это их аргументация на итераторах, что делает их очень многословными.


2

Если бы вы написали bar как функтор, было бы намного проще:

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

Здесь код вполне читабелен.


2
Это вполне читабельно, кроме того, что вы только что написали функтор.
Уинстон Эверт

Смотрите здесь, почему я думаю, что этот код хуже.
СБи

1

Я предпочитаю последнее, потому что это аккуратно и чисто. На самом деле это часть многих других языков, но в C ++ это часть библиотеки. Это не очень важно.


0

Теперь эта проблема на самом деле не относится к C ++, я бы сказал.

Дело в том, что передача некоторого функтора в еще одну функцию не предназначена для замены циклов .
Предполагается, что он упрощает определенные шаблоны, которые становятся очень естественными для функционального программирования, такие как посетитель и наблюдатель , просто чтобы назвать два, которые сразу приходят мне в голову.
В языках, в которых отсутствуют функции первого порядка (вероятно, Java является лучшим примером), такие подходы всегда требуют реализации некоторого заданного интерфейса, который является довольно многословным и избыточным.

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

someCollection.forEach(someUnaryFunctor);

Преимущество этого в том, что вам не нужно знать, как someCollectionна самом деле реализовано или что someUnaryFunctorделает. Все, что вам нужно знать, это то, что его forEachметод будет перебирать все элементы коллекции, передавая их данной функции.

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

Кроме того, вы должны иметь в виду, что функциональный подход медленнее, потому что у вас много вызовов, которые приходят с определенной стоимостью, которых у вас нет в цикле for.


1
«Кроме того, вы должны иметь в виду, что функциональный подход медленнее, потому что у вас много вызовов, которые приходят с определенной стоимостью, которых у вас нет в цикле for». ? Это утверждение не поддерживается. Функциональный подход не обязательно медленнее, тем более что под капотом могут быть оптимизации. И даже если вы полностью понимаете свой цикл, он, вероятно, будет выглядеть чище в более абстрактной форме.
Андрес Ф.

@Andres F: Так это выглядит чище? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));И это либо медленнее во время выполнения или компиляции (если вам повезет). Я не понимаю, почему замена структур управления потоками вызовами функций делает код лучше. О любой представленной здесь альтернативе более читабельно.
back2dos

0

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

Там, где есть алгоритм, который более точно выполняет то, что вы хотите, тогда да, вы должны отдавать предпочтение алгоритмам над рукописными циклами. очевидными примерами здесь являются сортировка с использованием sortили stable_sortили поиск с помощью lower_bound, upper_boundили equal_range, но большинство из них имеют некоторую ситуацию, в которой их предпочтительнее использовать, чем цикл с ручным кодированием.


0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.