函数对象与函数指针之间的区别

10

我有两个关于函数对象和函数指针的问题。


问题1:

当我查看STL中不同用法的sort算法时,我发现第三个参数可以是一个函数对象,以下是一个示例:

class State {  
  public:  
    //...
    int population() const;  
    float aveTempF() const;  
    //...  
};    
struct PopLess : public std::binary_function<State,State,bool> {  
    bool operator ()( const State &a, const State &b ) const  
        { return popLess( a, b ); }  
};  
sort( union, union+50, PopLess() );  

问题:

现在,语句sort(union, union+50,PopLess())是如何工作的?PopLess()必须被解析为类似于 PopLess tempObject.operator() 这样的内容,这与执行临时对象上的 operator () 函数相同。我将其视为将重载操作的返回值即 bool(如我的示例中所示)传递给 sort 算法。

那么,这种情况下,sort 函数如何解析第三个参数?


问题 : 2

问题 :

使用函数对象与函数指针相比,我们是否具有任何特定优势?如果我们使用下面的函数指针,它会产生任何不利影响吗?

inline bool popLess( const State &a, const State &b )
    { return a.population() < b.population(); }  
std::sort( union, union+50, popLess ); // sort by population

PS: 上面提到的参考资料(包括示例)均来自"Stephen C. Dewhurst"所著的书籍"C++ Common Knowledge: Essential Intermediate Programming"。
由于我无法理解主题内容,因此发布了求助帖。

非常感谢您的帮助。

6个回答

8

PopLess() 实例化了一个临时的 PopLess 类的实例,以便传递给 std::sort()。这与您说的效果基本相同(请注意,在此示例中会多次复制):

PopLess pl = PopLess();
sort(union, union + 60, pl);

然后,std::sort() 将调用该实例上的 operator()

至于函数对象或函数指针哪个更好,“更好”这一问题取决于具体情况。最重要的区别可能在于函数对象可以保持状态,而由指针传递的普通函数则不能。编译器可能能够更优化其中之一,但在大多数使用场景中,这可能并不重要。


普通函数通过指针传递无法以多种方式维护状态,例如:静态局部变量、静态模块变量(在C编译单元内的“全局”范围内,但从外部不可见),以及可能的其他方式? - Heath Hunnicutt
2
我认为詹姆斯所指的是,在函数指针级别上,没有为不同调用维护状态的范围。因此,如果您正在使用函数指针并多次调用排序,则不能为每个调用保留不同的状态。这可以通过函数对象实现。如果我错了,请纠正我。 - kumar_m_kiran
3
通常情况下,函数对象比函数指针更能提高代码的运行速度,因为编译器通常无法优化通过函数指针进行的调用,而通过函数对象中的operator()()成员函数进行调用可以轻松地进行内联。 - sbi
@Heath:使用全局变量是导致我们深陷困境的原因,促使有人发明了一个面向对象的好旧C语言版本。:) - sbi

5

问题1:

PopLess()必须被解析成类似于PopLess > tempObject.operator()的形式,这将与在临时对象上执行operator()函数相同。

不是[如你所说]扩展。实际上,PopLess()是对隐式PopLess::PopLess()构造函数的调用。此代码创建一个临时对象并将其传递给函数调用中的第3个参数。

问题2:

使用函数对象与使用函数指针相比,我们是否有任何特定优势?

在这种情况下没有。在这里,您的PopLess对象是无状态的。您可以创建具有内部状态的函数对象。

例如:

struct ensure_min
{
    int value;
    ensure_min(int val) : value(val) {}
    int operator()(const int& i)
    {
        return std::max(value, i);
    }
}

std::vector<int>  values;
values.push_back(-1);
values.push_back(0);
values.push_back(1);
values.push_back(2);
values.push_back(3);

std::transform(values.begin(), values.end(), 
    std::ostream_iterator<int>(std::cout, "\n"), ensure_min(1));

这段代码将输出序列中的所有数字,确保输出中的所有数字都具有最小值1(如果原始数字小于1,则输出为1;如果原始数字大于或等于1,则输出为原始数字)。


3

问题1: PopLess() 构造了一个类型为 PopLess 的对象,然后 sort 使用这个对象来排序范围内的元素,使用 operator ()

看一下 for_each 函数可能更容易,可以这样实现:

template <typename IterT, typename Function>
Function for_each( IterT first, IterT last, Function f ) {
    for( ; first != last; ++first )
        f(*first);

    return f;
}

基本上,for_eachsort以及使用函数对象的函数,只需获取您的函数对象实例并调用其operator ()即可。
问题2:当您使用函数对象而不是函数指针时,编译器可能能够通过内联来优化函数调用,这对于函数指针可能不是那么直接。此外,函数对象可以有状态。

1
我不会说通过函数指针的调用不能被优化掉。例如,如果算法在调用位置内联展开,并且在该位置使用的函数指针始终相同,则可以将通过函数指针的调用也内联。间接函数调用可能更难内联,但我不是编译器专家,所以我不太清楚。 - James McNellis
1
我尝试调整我的回答以反映您的评论,“不可能”是在你不知道的情况下使用的一个强烈的词。 - Jacob
对于内联点plus_one, 如果空间占用不是一个问题,当适合时启用内联,则编译器将内联调用operator()方法,因为它可以在编译时解析方法的调用从而生成更快的代码。如果operator()在一两个步骤中执行了一些简单任务,则确实建议这样做。 - sjsam

2
我不确定问题1在问什么,但PopLess()是一个对象。在排序函数内部,调用该对象的operator()方法来比较项。

0
主要的实际区别在于函数对象具有维护状态的能力。例如,在对多列数据进行排序时,函数对象可以拥有关于按哪一列排序、排序方向甚至排序规则(大小写敏感等)的信息。

0
  • 问题1:PopLess()是一个临时对象。
  • 问题2:它允许你的“函数”具有状态...在它被用作函数之前,你可以用任何你喜欢的方式初始化这个对象。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接