【问题标题】:Ruby blocks, java closures in C++Ruby 块,C++ 中的 java 闭包
【发布时间】:2009-06-21 13:22:55
【问题描述】:

我正在开发一个程序,我发现自己经常这样做:

void Model::SetCollideMode( const std::string &m )
{
  Body *body;

  std::map<std::string, Body* >::iterator iter;

  for (iter=this->bodies.begin(); iter!=this->bodies.end(); iter++)
  {
    body = iter->second;

    body->SetCollideMode( m );
  }
}

我在几个对象中有几个类似的方法,它们基本上将一个属性应用于它的所有子对象。来自 Ruby 世界,我渴望做类似的事情:

for_all_bodies{ body->SetCollideMode(m) }

有没有办法让这段代码更像闭包,或者以其他方式改进它?

我知道 C++ 是如何工作的,它是基于堆栈的,并且没有上下文信息来创建类似功能的完美闭包(这需要 VM 吗?)但至少比当前重复此代码改进 100 次编程。

【问题讨论】:

    标签: c++ stl iterator closures


    【解决方案1】:

    在 C++0x 中,是的。 See here. 正如你所猜到的,它们是以 C++ 特有的方式完成的,即如果你不小心关闭了一个堆栈变量,然后让 lambda 对象比堆栈存活得更长,那么你就有了未定义的行为。这是一种让你的程序崩溃的全新方法!但这是不公平的 - 在许多方面,它们比许多其他语言中的 lambdas 更复杂,因为您可以声明它们被允许改变状态的程度。

    在那之前,there have been attempts to emulate the same thing,但他们可能比他们的价值更麻烦。

    【讨论】:

    • "这是一种让您的程序崩溃的全新方法!"我认为这是一种令人兴奋的创新,它通过创建对堆栈变量的悬空引用来使程序崩溃,这是一种久经考验的方法。你“只是”需要记住闭包,连同指针或引用类型的返回语句和函数参数,有可能让引用逃脱它们的存储期限。
    【解决方案2】:

    有几种方法,没有一个是完美的。

    首先,“常规”方法是定义一个仿函数来执行您需要的操作:

    struct SetCollideModeFunc {
      SetCollideModeFunc(const std::string& m) : m(m) {}
      void operator()(std::pair<std::string, Body*>& p) {
        Body* b = p.second;
        b->SetCollideMode(m);
      }
    
      const std::string& m;
    };
    
    void Model::SetCollideMode( const std::string &m )
    {
      std::for_each(bodies.begin(), bodies.end(), SetCollideModeFunc(m));
    }
    

    这不会为您节省大量代码,但它确实允许您将迭代与要应用的操作分开。如果需要多次设置collidemode,当然可以复用functor。

    Boost.Lambda 库可以提供更短的版本,它允许您定义内联函子。我不记得确切的语法,因为我不经常使用 Boost.Lambda,但它会是这样的:

    std::for_each(bodies.begin(), bodies.end(), _1.second->SetCollideMode(m));
    

    在 C++0x 中,您可以获得对 lambdas 的语言支持,允许使用与此类似的语法,而无需引入第三方库。

    最后,Boost.ForEach 可能是一个选项,允许使用如下语法:

    void Model::SetCollideMode(const std::string &m)
    {
      BOOST_FOREACH ((std::pair<std::string, Body*> p), bodies) // note the extra parentheses. BOOST_FOREACH is a macro, which means the compiler would choke on the comma in the pair if we do not wrap it in an extra ()
      {
        p.second->SetCollideMode(m);
      }
    }
    

    【讨论】:

    • 别忘了 BOOST_FOREACH 是一个宏。成对的逗号会使编译器发疯。
    • 请注意费鲁乔提到的内容。它是一个宏,所以你在使用它时必须非常小心。
    • 即使有额外的括号 boost::BOOST_FOREACH 也会驱动编译器实例。您首先需要使用“typedef std::pair<:string body> something”,然后使用“BOOST_FOREACH (something &p, body) { ... }”
    • 很公平。这也是我通常不使用它的原因之一。它对我的口味来说太脆弱了。 ;)
    【解决方案3】:

    BOOST_FOREACH(或新的基于范围的循环)可能是要走的路,但这是我在当前标准中通常使用 tr1 绑定处理 lambda 的方式:

    #include <algorithm>
    #include <functional>
    using namespace std;
    
    void Model::SetCollideMode( const std::string &m )
    {
      for_each(bodies.begin(),bodies.end(),
               tr1::bind(&Body::SetCollideMode,
                         tr1::bind(&pair<std::string, Body*>::second, _1), m));
    }
    

    【讨论】:

    • oof,绑定语法总是让我头疼。我通常满足于编写单独的仿函数。这两种解决方案都不是很好。期待 0x 对 lambdas 的内置支持。 :)
    • 这真的很讨厌..我等不及 C++0x lamda 的了!
    • 这当然是一种后天习得的品味 :),但是在习惯了这种风格之后,在大多数情况下,我更喜欢它而不是自定义仿函数(重载函数或条件会变得更加混乱)
    【解决方案4】:

    你可以使用Boost.Foreach:

    #include <boost/foreach.hpp>
    
    void Model::SetCollideMode(const std::string &m)
    {
      typedef pair<std::string, Body*> body_t;
      BOOST_FOREACH (body_t& body, bodies)
      {
        body.second->SetCollideMode(m);
      }
    }
    

    【讨论】:

    • 我认为这将是一个循环对 <:string body>
    • 没错,我忘了是地图。
    • 你还必须有 typedef 对 body_t;如果你想使用参考语法(body_t& body)
    【解决方案5】:

    C++ 还不支持 lambda。我有时会使用这种解决方法:

    #include <boost/bind.hpp>
    void Model::SetCollideMode( const std::string &m )
    {    
      typedef std::map<std::string, Body* > Bodies;
      struct Helper
      {
          static SetCollideMode(const std::pair<std::string, Body*> & value,
                                const std::string & m)
          {
              value.second->SetCollideMode(m);
          }
      };
    
      for_each(bodies.begin(),
               bodies.end(),
               boost::bind(Helper::SetCollideMode,_1, m));
    }
    

    只要我的 2 美分..

    【讨论】:

    • 你确定你真的做过吗?您不能在局部类型的静态成员函数中引用封闭函数的局部变量(在这种情况下是您的“主体”指针)。为什么不只是有:value->second->SetCollideMode(m);
    • 糟糕,是的,这确实行不通。确实,value->second->SetCollideMode(m);将工作。我使用了“body”变量,因为我想尽可能地保留原始示例(没有意识到我无法在该范围内访问它)。
    猜你喜欢
    • 2010-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-02-10
    • 2011-09-02
    • 1970-01-01
    • 2016-02-24
    相关资源
    最近更新 更多