English 中文(简体)
std::map + std::tr1::bind + 标准算法值得吗?
原标题:
  • 时间:2008-12-17 21:50:57
  •  标签:

这是我昨天关于问题的跟进。我心中一直有Scott Meyers关于只写代码的警告。原则上,我喜欢使用标准算法来访问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似乎更容易阅读,但可能需要付出一些小的运行时代价。我不需要CPU时间,所以我更倾向于选项2。你们同意吗?还有我应该考虑的第三个选项吗?

附言:在撰写此问题的过程中,我得出结论,最好的方法(对于我的项目)是将std::map的键存储在一个侧面容器中,并在其上进行迭代。然而,可维护性问题仍然存在。

最佳回答

清晰总比聪明要好。做你可以以后再阅读的事情。

你不是唯一一个认为标准代码有点晦涩的人。下一个C++标准将引入lambda函数,这样你就可以使用标准算法编写更易读的代码。

问题回答

第一个与第二个一样易读和易维护,但是如果你知道 bind的作用。我已经使用Boost::Bind(基本上相同于std::tr1::bind)很长一段时间,以至于我对它没有问题。

一旦TR1成为正式标准的一部分,您可以安全地假设任何有能力的C++程序员都会理解它。在此之前,它可能会带来一些困难,但我始终考虑长期而非短期。

你忘记了 using namespace std::tr1::placeholders :P

说实话,对于像这样的简单算法,后者代码可能更容易维护。但我实际上倾向于前者(尤其是当C++1x给我们lambda时!),因为它强调了一种函数式编程风格,我个人更喜欢使用循环的命令式风格。

这真的是一种不同的方式;标准算法最有用的时候要么是复杂的,要么是通用的,但这个算法都不是。

这是使用lambda表达式将其转换成的样子:

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)
                  )
             );

而且使用lambda表达式(这可能是所有选项中最整洁和最明确的):

std::for_each(m.begin(), m.end(),
              [&v](MyMap::value_type pair){v.push_back(pair.first);}
             );

我说2号。

为了提高性能,您可以将m.end()从循环中取出,并在向量中保留空间。

迫不及待地等待C++0x和基于范围的for循环的到来;这将使你的循环更加出色。

选择方案1,参考Scott Meyers的《Effective STL》第43项,第181页。

当我昨天看到你的问题时,让我看两次才能理解代码的不是我经常使用的bind,而是map :: value_type :: first,我很少用到它。尽管我同意“清晰胜于巧妙”,但在清晰之前需要熟悉,你不会变得熟悉你不使用的样式...

我还想说,虽然选项2在理解预期目的方面更清晰,但它更容易隐藏错误(选项1中的任何错误更有可能在编译时可见)。

我会选择第三个选项:

#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可以在 v 上调用 reserve()

  4. 不需要冗余的 v.begin()v.end()对。

任何其它方式都是纯疯狂。





相关问题