【问题标题】:Correct Way to Define a Predicate Function in C++在 C++ 中定义谓词函数的正确方法
【发布时间】:2011-07-28 04:19:53
【问题描述】:

我正在尝试编写用于 STL 算法的谓词函数。我看到它们是定义谓词的两种方式:

(1) 使用如下简单函数:

bool isEven(unsigned int i)   
{ return (i%2 == 0); }

std::find_if(itBegin, itEnd, isEven); 

(2) 使用operator()函数如下:

class checker {  
public:  
  bool operator()(unsigned int i)  
  { return (i%2 == 0); }  
}; 

std::find_if(itBegin, itEnd, checker); 

我对第二种类型有更多用途,因为我通常想创建一个包含一些成员的谓词对象并在算法中使用它们。当我在检查器中添加相同的 isEven 函数并将其用作谓词时,出现错误:
3.给出错误的语法:

class checker { 
    public: 
       bool isEven(unsigned int i) 
       { return (i%2 == 0); }
}; 

checker c; 
std::find_if(itBegin, itEnd, c.isEven); 

在编译过程中调用 c.isEven 会出错,说明未定义对某些函数的引用。有人可以解释为什么 3. 给出错误吗?另外,如果有任何关于谓词和迭代器基础知识的指南,我将不胜感激。

【问题讨论】:

  • 查看您的编译器是否足够现代以支持使用 lambda,例如:std::find_if(itBegin, itEnd, [](int i){ return i%2 == 0;});
  • @jerry - 谢谢..看起来是个很酷的主意。但我想我们正在牺牲对异国语法的代码重用:)。如果我有一个仿函数,我以后可以重复使用它,并且更容易维护
  • 是的——如果你有一个很有可能在很多地方使用的函子,那么 lambda 可能不是最好的路线。同时,对于像这样的微不足道的代码,直接可见的收益可以超过重复带来的(可能的)损失。同时,我必须补充一点,最好不要将语法视为“异国情调”——包含它的新标准正在最终确定,并且大多数当前的编译器已经支持它;期待很快看到它被广泛使用。

标签: c++ stl predicate


【解决方案1】:

指向成员函数的指针需要调用实例,并且您仅将成员函数指针传递给std::find_if(实际上您的语法不正确,因此它根本不起作用;正确的语法是std::find_if(itBegin, itEnd, &checker::isEven) 然后由于我给出的原因仍然不起作用)。

find_if 函数期望能够使用单个参数(要测试的对象)调用该函数,但它实际上需要两个来调用成员函数:实例 this 指针和要比较的对象。

重载operator() 允许您同时传递实例和函数对象,因为它们现在是同一个东西。使用成员函数指针,您必须将两条信息传递给只需要一条信息的函数。

有一种方法可以使用std::bind(需要<functional> 标头):

checker c;
std::find_if(itBegin, itEnd, std::bind(&checker::isEven, &c, std::placeholders::_1));

如果您的编译器不支持std::bind,您也可以为此使用boost::bind。虽然这样做与重载 operator() 相比并没有真正的优势。


更详细地说,std::find_if 需要一个与签名 bool (*pred)(unsigned int) 匹配的函数指针或类似的行为。它实际上不需要是一个函数指针,因为谓词的类型是由模板绑定的。任何表现得像 bool (*pred)(unsigned int) 的东西都是可以接受的,这就是函子起作用的原因:它们可以用单个参数调用并返回 bool

正如其他人指出的那样,checker::isEven 的类型是 bool (checker::*pred)(unsigned int),它的行为与原始函数指针不同,因为它需要调用 checker 的实例。

一个指向成员函数的指针在概念上可以被认为是一个带有附加参数的常规函数​​指针,this 指针(例如bool (*pred)(checker*, unsigned int))。您实际上可以使用std::mem_fn(&checker::isEven)(也来自<functional>)生成一个可以以这种方式调用的包装器。这仍然对你没有帮助,因为现在你有一个函数对象,它必须使用两个参数而不是一个参数来调用,std::find_if 仍然不喜欢。

使用std::bind 会将指向成员函数的指针视为将this 指针作为其第一个参数的函数。传递给std::bind 的参数指定第一个参数应始终为&c,第二个参数应绑定到新返回的函数对象的第一个参数。此函数对象是一个可以使用一个参数调用的包装器,因此可以与std::find_if 一起使用。

虽然std::bind 的返回类型未指定,但如果您需要显式引用绑定的函数对象而不是将其直接传递给像我这样的另一个函数,则可以将其转换为std::function<bool(unsigned int)>(在这种特殊情况下)在我的例子中做了。

【讨论】:

    【解决方案2】:

    我猜是因为c.isEven()的类型是,

    bool (checker::*)(unsigned int) // member function of class
    

    find_if() 可能没有预料到。 std::find_if 应该期待一个函数指针 (bool (*)(unsigned int)) 或一个函数对象。

    编辑:另一个约束:非static 成员函数指针必须由class 对象调用。在您的情况下,即使您成功传递了成员函数,那么 find_if() 仍然不会有任何关于任何 checker 对象的信息;所以为了接受成员函数指针参数而重载find_if() 是没有意义的。

    注意:一般c.isEven不是传递成员函数指针的正确方式;它应该被传递为&checker::isEven

    【讨论】:

    • 我了解 STL 算法。正在寻找函数指针。但是为什么语法 1. 调用诸如 isEven 之类的常规函数​​会起作用。为什么它被g ++视为函数指针..
    • @srikrish,因为在语法 1 中你有独立的功能;它不依赖于任何 class 对象。如果您看到find_if (cplusplus.com/reference/algorithm/find_if) 的定义,您可以看到predicate 是一个template 参数。可以解析为函数指针(不是类成员函数指针)。
    【解决方案3】:

    checker::isEven 不是函数;它是一个成员函数。如果不引用checker 对象,就不能调用非静态成员函数。因此,您不能只在可以传递函数指针的任何旧地方使用成员函数。成员指针具有特殊语法,不仅需要 () 来调用。

    这就是函子使用operator()的原因;这使得对象无需使用成员函数指针即可调用。

    【讨论】:

    • 我已经调用了 isEven,它是一个带有 c 的非静态成员函数(它是检查器的对象) - 我不明白“你不能在没有引用的情况下调用非静态成员函数检查对象”。
    • @srikrish:调用函数和将函数传递给将要调用它的其他人之间是有区别的。您可以通过对象调用成员函数。但是您不能将成员函数传递给将调用它的其他代码,除非该代码知道它接收成员函数。 std::find_if 需要传递一些它可以使用() 调用的东西,并且成员函数是不够的。
    【解决方案4】:

    我更喜欢functors(函数对象),因为让您的程序更具可读性,更重要的是,可以清楚地表达意图。

    这是我最喜欢的例子:

    template <typename N>
    struct multiplies
    {
      N operator() (const N& x, const N& y) { return x * y; }
    };
    
    vector<int> nums{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    // Example accumulate with transparent operator functor 
    double result = accumulate(cbegin(nums), cend(nums), 1.1, multiplies<>());
    

    注意:近年来我们得到了lambda expression 的支持。

    // Same example with lambda expression
    double result = accumulate(cbegin(nums), cend(nums), 1.1,
                                [](double x, double y) { return x * y; });
    

    【讨论】:

    • 为什么在调用accumulate() 时使用1.1 而不是1?
    【解决方案5】:

    给出的示例表明您应该使用调用运算符 (operator()),而在您的示例中,您调用了函数 isEven。尝试将其重写为:

    class checker { 
        public: 
           bool operator()(unsigned int i) 
           { return (i%2 == 0); }
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-06
      • 1970-01-01
      • 1970-01-01
      • 2022-01-01
      • 2016-06-10
      相关资源
      最近更新 更多