在C ++中定义谓词函数的正确方法

时间:2011-07-28 04:19:54

标签: c++ stl predicate

我正在尝试编写用于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); 

我更多地使用第二种类型,因为我通常想创建一个带有一些成员的谓词对象并在算法中使用它们。当我在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.给出错误?另外,我将非常感谢有关谓词和迭代器基础知识的任何指示。

5 个答案:

答案 0 :(得分:10)

指向成员函数的指针需要调用实例,并且您只将成员函数指针传递给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)>(在此特定情况下)像我在我的例子中所做的另一个功能。

答案 1 :(得分:4)

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

这就是仿函数使用operator()的原因;这使得对象可以调用,而不必使用成员函数指针。

答案 2 :(得分:4)

我想这是因为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传递。

答案 3 :(得分:2)

我更喜欢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; });

答案 4 :(得分:1)

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

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