Стоит ли использовать std :: map + std :: tr1 :: bind + стандартные алгоритмы? - PullRequest
7 голосов
/ 18 декабря 2008

Это продолжение моего вчерашнего вопроса . У меня на уме предупреждение Скотта Майерса о коде только для записи. Мне в принципе нравится идея использования стандартных алгоритмов для доступа к ключам или значениям std :: map, но требуемый синтаксис немного барокко ИМХО. Допустим, я хочу сбросить все ключи карты в вектор. Учитывая следующие декларации,

typedef std::map<int, int> MyMap;
MyMap m;
std::vector<int> v;

какой код более удобен в обслуживании (т. Е. Потенциально менее запутанен)?

Вариант № 1:

std::transform(m.begin(),
               m.end(),
               std::back_inserter(v),
               std::tr1::bind(&MyMap::value_type::first, _1));

Вариант № 2:

for (MyMap::iterator i = m.begin(); i != m.end(); ++i)
{
    v.push_back(i->first);
}

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

P.S. В ходе написания этого вопроса я пришел к выводу, что лучший способ (для моего проекта) прочитать ключи std :: map - это сохранить их в боковом контейнере и выполнить итерации по нему. Вопрос о ремонтопригодности все еще стоит.

Ответы [ 7 ]

11 голосов
/ 18 декабря 2008

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

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

5 голосов
/ 18 декабря 2008

Первый так же удобен для чтения и обслуживания, как и второй - , если , вы знаете, что делает bind. Я работал с Boost :: Bind (по сути идентичным std::tr1::bind) достаточно долго, чтобы у меня не было с этим проблем.

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

4 голосов
/ 18 декабря 2008

Вы забыли using namespace std::tr1::placeholders: P

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

Это действительно другая вещь ударов; Стандартные алгоритмы наиболее полезны, когда они являются либо сложными, либо общими, и это ни то, ни другое.

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

std::transform(m.begin(), m.end(), std::back_insterter(v),
               [](MyMap::value_type pair){ return pair.first; }
              );

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

using std::tr1::bind;
using std::tr1::placeholders::_1;
std::for_each(m.begin(), m.end(),
              bind(&std::vector<int>::push_back, v,
                   bind(&MyMap::value_type::first, _1)
                  )
             );

И с лямбдами (это, вероятно, в общем и целом самый аккуратный и явный из всех вариантов):

std::for_each(m.begin(), m.end(),
              [&v](MyMap::value_type pair){v.push_back(pair.first);}
             );
3 голосов
/ 18 декабря 2008

я говорю идти на 2)

Для повышения производительности вы можете вывести m.end() из цикла и зарезервировать место в векторе.

Не могу дождаться C ++ 0x и цикла for на основе диапазона; это сделало бы ваш цикл еще лучше.

1 голос
/ 24 марта 2012

Я бы выбрал вариант № 3:

#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/push_back.hpp>

boost::push_back(v, m | boost::adaptors::map_keys);

Это имеет следующие преимущества:

  1. короче

  2. использует именованную функцию для получения ключей

  3. (потенциально) более эффективен (поскольку boost::push_back может вызвать reserve() на v)

  4. и не требует избыточной пары v.begin(), v.end().

Любой другой путь - это чистое безумие.

1 голос
/ 18 декабря 2008

Когда я вчера посмотрел на ваш вопрос, не связывание (которым я часто пользуюсь) заставило меня дважды взглянуть на код, но map = value_type :: first, которого у меня не было. использовать очень часто. Хотя я согласен с тем, что «Ясность всегда побеждает умнее», перед ясностью требуется знакомство, и вы не собираетесь знакомиться со стилями, которые не используете ...

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

1 голос
/ 18 декабря 2008

Перейдите к варианту № 1, см. Скотт Мейерс, Действующий STL Пункт № 43, стр. 181.

...