【问题标题】:How do you use stl's functions like for_each?你如何使用stl的for_each等函数?
【发布时间】:2010-05-18 11:11:09
【问题描述】:

我开始使用 stl 容器,因为当我需要列表、集合和映射的功能并且在我的编程环境中没有其他可用的功能时,它们非常方便。我不太关心它背后的想法。 STL 文档在涉及函数等方面都很有趣。然后我跳过阅读,直接使用容器。

但昨天,我的假期仍然很放松,我只是试了一下,想多走一点 stl 方式。所以我用了transform函数(能不能给我点个掌声,谢谢)。

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

我学到了一个艰难的方法,即您应该始终选择解决手头问题的最简单方法。例如,我看不到 for_each 函数为我做了什么,这证明了在正常循环内的几行简单代码上使用辅助类是合理的,这样每个人都可以看到发生了什么。

我想知道,您对我的担忧有何看法?当您开始以这种方式工作时,您是否像我一样看到它,并且在您习惯它时改变了主意?有没有我忽略的好处?还是你只是像我一样忽略这些东西(并且可能会继续这样做)。

谢谢。

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

【问题讨论】:

  • std::mem_fun 和类似的帮助,但这是关于 C++ IMO 的痛苦事情之一。 C++0x lambdas 将对此有所帮助。当您尝试使用 std::bind1st 和组合之类的东西时,情况会变得更糟。
  • 我不认为 BOOST_FOR_EACH 只是为了方便。它比您通常的循环更简洁,更具表现力。这是一个真正的改进。我认为 lambdas 将使 STL 的使用更加容易,并将其提升到应有的水平。

标签: c++ function stl


【解决方案1】:

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

在某种程度上,你是对的。这就是为什么即将到来的 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 添加到语言中的原因。

【讨论】:

  • 我认为在 60% 的情况下,仿函数将包含不超过 3 行代码,并且可能不会在其他地方重用(没有人会搜索这些以重用它们,因为这些仿函数的名称很快就变得一团糟)。在另外 30% 的情况下,传输到函子的代码是不平凡的,但在代码库中是唯一的,无需将其重构为对象。正如我所看到的,在少数情况下(
  • 我同意 thomas-gies 的观点。我通常在使用它们的方法中定义我的函子,因为它们没有在其他任何地方使用。
  • return val+1 后缺少分号 ;)
  • 当您需要捕获调用方法中的一些变量时,仿函数也会变得混乱。我发现我经常需要为函子捕获一个变量。要捕获变量,您需要在仿函数中声明适当的成员,并且需要确保这些成员在调用方法中已初始化。这既容易出错又很麻烦,应该由编译器真正处理。当然,C++0x 会用 lambdas 解决这个问题。
  • @thomas-gies:是的,但不使用仿函数,您还必须编写循环本身(这是另外 2-3 行代码)。您正在编写的函数变得更加混乱。因此,为了简化和缩短您正在编写的代码,将循环内容拉出到单独定义的函子中可能是值得的。但是,是的,这很不方便。有时不方便。这就是为什么我很高兴 VC2010 支持 lambdas ;)
【解决方案2】:

我发现它与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;
}

【讨论】:

    【解决方案3】:

    您会发现专家之间存在分歧,但我想说for_eachtransform 有点让人分心。 STL 的强大之处在于将非平凡算法与被操作的数据分开。

    Boost 的 lambda 库绝对值得一试,看看您是如何使用它的。然而,即使您发现语法令人满意,所涉及的大量机器在编译时间和调试能力方面也存在缺点。

    我的建议是使用:

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

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

    【讨论】:

      【解决方案4】:

      为序列的每个元素增加一个计数器对于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&lt;SinkFactory&gt;::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)。

      【讨论】:

        【解决方案5】:

        我猜 C++ 礼遇也有同样的担忧。待验证的新 C++0x 标准引入了 lambda。这一新功能将使您能够在直接在算法参数列表中编写简单的辅助函数时使用算法。

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

        【讨论】:

          【解决方案6】:

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

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

          IMO,这对于一个增量来说太复杂了,以普通的普通 for 循环的形式编写它会更清晰。但是,当您要对序列执行的操作变得更加复杂时。然后我发现将要对每个元素执行的操作与实际的循环语句清楚地分开是很有用的。如果你的函子名称选择得当,代码会得到一个描述性的加号。

          【讨论】:

          • 这有助于解决“我应该把函子放在哪里?”问题。但是,如果类 Increment 需要有关上下文的信息,您仍然需要声明成员和构造函数等。或者这种方法也可以避免这种情况?在 ruby​​ 中,块可以访问上下文的堆栈。
          • @thomas-gies:这种方法在上下文访问方面存在一些限制。详情请看这个:informit.com/guides/content.aspx?g=cplusplus&seqNum=191.
          • 你编译过这个吗?在 C++ 98 中,本地类不能用作模板参数。在 C++ 0x 中它们可以,但是我们有 lambdas。
          【解决方案7】:

          这些确实是真正的问题,并且这些问题正在下一版本的 C++ 标准(“C++0x”)中得到解决,该版本应该在今年年底或 2011 年发布。该版本的 C++ 引入了一个名为C++ lambdas 的概念,它允许在另一个函数中构造简单的匿名函数,这使得在不将代码分解成小块的情况下很容易完成你想要的事情。从 GCC 4.5 开始,GCC (实验性地?)支持 Lambda。

          【讨论】:

            【解决方案8】:

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

            作为这些库的用户——您不打算改造 .NET 是吗? -- 你可以使用他们的简化版。

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

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

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

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-11-21
              相关资源
              最近更新 更多