你如何使用像for_each这样的stl函数?

时间:2010-05-18 11:11:09

标签: c++ function stl

我开始使用stl容器,因为当我需要列表,设置和映射的功能并且在我的编程环境中没有任何其他功能时,它们非常方便。我并不关心它背后的想法。 STL文档很有意思,直到功能等等。然后我跳过了阅读,只是使用了容器。

但昨天,我的假期仍在放松,我只是尝试了一下,想要更多地走一段路。所以我使用了转换功能(我可以为我鼓掌一下,谢谢你)。

从学术角度来看,它看起来很有趣,而且很有效。但困扰我的是,如果你加强了对这些函数的使用,你需要成千上万的辅助类,而不是你想在代码中做的所有事情。程序的整个逻辑被切成小块。这种切片不是良好的编码习惯的结果;这只是技术需求。有些东西,这让我的生活可能更难,也不容易。

我学到了很难的方法,你应该总是选择最简单的方法来解决手头的问题。我无法看到,例如,for_each函数正在为我做什么,证明在几个简单的代码行中使用辅助类是合理的,这些代码位于一个普通的循环中,这样每个人都可以看到正在发生的事情。

我想知道,你在考虑我的担忧?当你开始以这种方式工作并且习惯了之后改变主意时,你有没有像我一样看到它?我忽略了哪些好处?或者你就像我一样忽略这些东西(并且可能继续这样做)。

感谢。

PS:我知道在boost中有一个真正的for_each循环。但我在这里忽略它,因为它只是一种方便的方式,我猜通常使用迭代器循环。

8 个答案:

答案 0 :(得分:7)

  

程序的整个逻辑被切成小块。这种切片不是良好编码习惯的结果。这只是技术需求。有些东西,这让我的生活可能更难,也不容易。

你在某种程度上是对的。这就是为什么即将推出的C ++标准修订版将添加lambda表达式,允许你做这样的事情:

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函数。有时这不值得努力,这就是为什么lambdas被添加到语言中。

答案 1 :(得分:3)

我发现它与boost::bindboost::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 :(得分:3)

你会发现专家之间的分歧,但我会说for_eachtransform有点分散注意力。 STL的强大之处在于将非平凡算法与正在运行的数据分离。

Boost的lambda库绝对值得尝试,看看你如何继续它。但是,即使您发现语法令人满意,所涉及的大量机器在编译时和调试能力方面也存在缺陷。

我的建议是使用:

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

而不是for_eachtransform,但更重要的是要熟悉 非常有用的算法:sortunique,{ {1}}随机选择三个。

答案 3 :(得分:2)

为序列的每个元素增加一个计数器不是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是提升的一部分,但实现很简单,看起来像这样:

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

答案 4 :(得分:1)

我猜C ++ comity有同样的顾虑。要验证的新C ++ 0x标准引入了lambdas。这个新功能使您可以在算法参数列表中直接编写简单的辅助函数时使用该算法。

std::transform(in.begin(), int.end(), out.begin(), [](int a) { return ++a; })

答案 5 :(得分:1)

本地课程是解决这个问题的一个很好的功能。例如:

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

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

IMO,这对于一个增量来说太复杂了,并且以常规的 循环的形式编写它会更加清晰。但是当你想要在一个序列上执行的操作变得非常复杂时。然后我发现将实际循环句子中每个元素执行的操作清楚地分开是有用的。如果正确选择了函子名称,代码将获得描述性加号。

答案 6 :(得分:0)

这些确实是真正关注的问题,这些问题将在下一版C ++标准(“C ++ 0x”)中得到解决,该版本应在今年年底或2011年发布。该版本的C ++引入一个名为C++ lambdas的概念,它允许一个人在另一个函数中构建简单的匿名函数,这使得很容易实现你想要的东西,而不会将你的代码分解成小块。自GCC 4.5起,Lambda在(实验上?)支持GCC。

答案 7 :(得分:0)

STL Boost 这样的库很复杂,因为它们需要解决所有需求并在任何平台上工作。

作为这些图书馆的用户 - 您不打算重建.NET吗? - 你可以使用简化的礼物。

这可能是我喜欢使用的Boost中更简单的 foreach

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

看起来比使用.begin().end()等更整洁,更简单,但它适用于几乎任何可迭代的集合(不仅仅是数组/向量)。 / p>