【问题标题】:Idiomatic C++ for remove_ifremove_if 的惯用 C++
【发布时间】:2011-06-07 09:12:43
【问题描述】:

我有这门课

class Point2D
{
public:
 bool isValid();
 // ...
private:
 double x_, y_;
};

我有一个std::vector< Point2D >,我想删除无效点,现在我这样做:

bool invalid ( const Point2D& p )
{
 return !p.isValid();
}

void f()
{
 std::vector< Point2D > points;
 // fill points
 points.erase( std::remove_if( points.begin(), points.end(), invalid ), points.end() );
 // use valid points
}

是否有一种标准的方式来做到这一点(漂亮地),例如不需要“复制”类方法Point2D::isValid 的功能?

也许使用 C++11 lambda(我对 lambda 不是很熟悉)?

【问题讨论】:

    标签: c++ c++11


    【解决方案1】:

    试试这个:

    points.erase(std::remove_if(points.begin(), 
                                points.end(),
                                std::not1(std::mem_fun_ref(&Point2D::isValid))), 
                 points.end());
    

    【讨论】:

      【解决方案2】:

      不完全标准,但几乎:您可以使用 boost::bind 并执行以下操作

      points.erase( std::remove_if( points.begin(), points.end(),
        !boost::bind(&Point2D::isValid, _1 )), points.end() );
      

      顺便说一句,你应该声明 isValid 方法为 const。

      【讨论】:

      • +1 在这种情况下,我更喜欢使用绑定而不是 lambda,因为您不需要复制签名。由于 OP 似乎允许 C++0x 的东西,std::bind 可能是一个更好的选择。
      • 我很好奇您所说的“不会真正起作用”是什么意思。这是一个记录在案的功能(cf boost.org/doc/libs/1_46_1/libs/bind/bind.html),我以前使用过它没有问题
      • @ltjax 此功能不适用于std::bind,它仅适用于 Boost。
      【解决方案3】:

      lambda 版本也不会更干净,但它还有另一个重要优势:locality。您会看到使用它的代码:

      points.erase( std::remove_if( points.begin(), points.end(),
                    [](const Point2D& p){
                      return !p.isValid();
                    }), points.end() );
      

      请注意,您需要更改 isValid 以使其成为 const 函数,否则您无法在引用到 const (const Point2D&amp;) 时调用它。
      另一种选择是为您的班级实施operator!

      class Point2D{
        // ... as before
      public:
        bool isValid() const;
      
        bool operator!() const{
          return !isValid();
        }
      };
      

      注意,这两个函数都是常量。现在你可以实现一个通用的否定函子:

      struct negate{
        template<class T>
        bool operator()(T const& t){
          return !t;
        }
      };
      

      然后使用它:

      points.erase( std::remove_if( points.begin(), points.end(), negate()), points.end() );
      

      【讨论】:

      • 即使答案是正确的,我还是建议反对滥用运算符重载。至少,如果使用,它应该与 Safe Bool Idiom 结合使用。
      • @Matthieu M. +1 我同意,我不太喜欢运算符重载,我发现 remove_if 中的“否定”不太可读
      • 要扩展 Matthieu M. 的评论,请参阅 en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool c++11 标准通过允许显式转换运算符提供了一个优雅的解决方案。
      【解决方案4】:

      你可以使用std::mem_fun_refstd::not1的组合来做你想做的事:

      points.erase( std::remove_if( points.begin(), points.end(),
                                    std::not1( std::mem_fun_ref( &Point2D::isValid ) ) ),
                    points.end() );
      

      对于它的价值,唯一的“惯用”部分是erase-remove idiom

      【讨论】:

        【解决方案5】:

        如果 Boost 适合您,请使用 @Randall Flagg 的建议和 boost::remove_erase_if

        boost::remove_erase_if(points, !boost::bind(&Point2D::isValid, _1));
        

        【讨论】:

        • @monkey:调用仿函数时传递的第一个参数。在这种情况下,考虑的是point,我想将其绑定到this,以便绑定大致相当于!_1-&gt;Point2D::isValid()
        【解决方案6】:

        我想你在找not1

        编辑:仔细查看您的示例,我认为您无法以其他任何方式做到这一点,因为isValid() 是一个成员函数。

        【讨论】:

        • 这也是我的第一反应!但我不认为它在这里直接适用,因为 OP 想在元素上调用成员函数。
        • 我觉得你可以用not1(mem_fun_ref(&amp;Point2D::isValid))
        猜你喜欢
        • 2014-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多