Как вы используете функции stl, такие как for_each? - PullRequest
5 голосов
/ 18 мая 2010

Я начал использовать контейнеры stl, потому что они очень пригодились, когда мне понадобились функции списка, набора и сопоставления, и в моей среде программирования больше ничего не было доступно. Меня не заботили идеи, стоящие за этим. Документация STL была интересна до того момента, когда она доходила до функций и т. Д. Затем я пропустил чтение и просто использовал контейнеры.

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

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

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

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

Спасибо.

PS: я знаю, что в boost есть настоящий цикл for_each. Но я игнорирую это здесь, так как это просто удобный способ для моих обычных циклов с итераторами.

Ответы [ 8 ]

7 голосов
/ 18 мая 2010

Вся логика программы нарезана на мелкие кусочки. Эта нарезка не является результатом хороших привычек кодирования. Это просто техническая необходимость. Что-то, что делает мою жизнь, вероятно, тяжелее, а не легче.

Вы правы, в определенной степени. Вот почему грядущая версия стандарта C ++ добавит лямбда-выражения, что позволит вам сделать что-то вроде этого:

std::for_each(vec.begin(), vec.end(), [&](int& val){val++;})

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

Выполнение вышеупомянутого сегодня будет выглядеть так:

int incr(int& val) { return val+1}

// and at the call-site
std::for_each(vec.begin(), vec.end(), incr);

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

  • какая операция выполняется (если она названа соответствующим образом)
  • какие элементы затронуты

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

3 голосов
/ 18 мая 2010

Вы найдете несогласие среди экспертов, но я бы сказал, что for_each и transform немного отвлекают. Сила STL заключается в отделении нетривиальных алгоритмов от данных, с которыми ведется работа.

Лямбда-библиотека Boost определенно стоит поэкспериментировать, чтобы узнать, как вы с ней справляетесь. Однако, даже если вы найдете синтаксис удовлетворительным, огромное количество задействованного оборудования имеет недостатки с точки зрения времени компиляции и возможности отладки.

Мой совет - используйте:

for (Range::const_iterator i = r.begin(), end = r.end(); i != end(); ++i)
{
   *out++ = ..   // for transform
}

вместо for_each и transform, но, что более важно, ознакомьтесь с алгоритмами, которые очень полезны : sort, unique, rotate, чтобы выбрать три случайным образом.

3 голосов
/ 18 мая 2010

Я считаю, что это наиболее полезно, когда используется вместе с boost::bind и boost::lambda, так что мне не нужно писать собственный функтор. Это всего лишь маленький пример:

class A
{
public:
    A() : m_n(0)
    {
    }

    void set(int n)
    {
        m_n = n;
    }

private:
    int m_n;
};

int main(){    

    using namespace boost::lambda;

    std::vector<A> a;
    a.push_back(A());
    a.push_back(A());

    std::for_each(a.begin(), a.end(), bind(&A::set, _1, 5));


    return 0;
}
2 голосов
/ 18 мая 2010

Увеличение счетчика для каждого элемента последовательности не является хорошим примером для for_each.

Если вы посмотрите на лучшие примеры, вы можете обнаружить, что код делает код более понятным для понимания и использования.

Это код, который я написал сегодня:

// assume some SinkFactory class is defined
// and mapItr is an iterator of a std::map<int,std::vector<SinkFactory*> >

std::for_each(mapItr->second.begin(), mapItr->second.end(),
    checked_delete<SinkFactory>);

checked_delete является частью boost, но реализация тривиальна и выглядит так:

template<typename T>
void checked_delete(T* pointer)
{
    delete pointer;
}

Альтернативой было бы написать это:

for(vector<SinkFactory>::iterator pSinkFactory = mapItr->second.begin();
    pSinkFactory != mapItr->second.end(); ++pSinkFactory)
    delete (*pSinkFactory);

Более того, после того, как вы написали checked_delete один раз (или если вы уже используете boost), вы можете удалить указатели в любой последовательности где угодно, с тем же кодом, не заботясь о том, какие типы вы перебираете (что вы не должны объявлять vector<SinkFactory>::iterator pSinkFactory).

Существует также небольшое улучшение производительности из-за того, что для for_each container.end() будет вызываться только один раз, и потенциально большие улучшения производительности в зависимости от реализации for_each (это может быть реализовано по-разному в зависимости от полученного тега итератора ).

Кроме того, если вы комбинируете boost :: bind с алгоритмами последовательности stl, вы можете создавать всевозможные забавные вещи (см. Здесь: http://www.boost.org/doc/libs/1_43_0/libs/bind/bind.html#with_algorithms).

1 голос
/ 18 мая 2010

Местные классы - отличная возможность для решения этой проблемы. Например:

void IncreaseVector(std::vector<int>& v)
{
 class Increment
 {
 public:
  int operator()(int& i)
  {
   return ++i;
  }
 };

 std::for_each(v.begin(), v.end(), Increment());
}

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

1 голос
/ 18 мая 2010

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

std::transform(in.begin(), int.end(), out.begin(), [](int a) { return ++a; })
0 голосов
/ 18 мая 2010

Такие библиотеки, как STL и Boost , также сложны, потому что они должны решать любые задачи и работать на любой пластине.

Как пользователь этих библиотек - вы не планируете переделывать .NET, не так ли? - Вы можете использовать их упрощенные вкусности.

Вот, возможно, более простое foreach от Boost, которое я хотел бы использовать:

BOOST_FOREACH(string& item in my_list)
{
    ...
}

Выглядит намного аккуратнее и проще , чем при использовании .begin(), .end() и т. Д., И все же он работает практически для любой повторяемой коллекции (не только для массивов / векторов).

0 голосов
/ 18 мая 2010

Это действительно реальные проблемы, и они рассматриваются в следующей версии стандарта C ++ («C ++ 0x»), которая должна быть опубликована либо в конце этого года, либо в 2011 году. Эта версия C ++ вводит понятие, называемое C ++ lambdas , которое позволяет создавать простые анонимные функции внутри другой функции, что позволяет очень легко выполнять то, что вы хотите, не разбивая ваш код на мелкие кусочки. Лямбды (экспериментально?) Поддерживаются в GCC начиная с GCC 4.5.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...