我该使用函数还是无状态函数对象?

11

这两段代码执行的是同样的功能,并且它们将在排序函数中被使用,正如你所看到的。 哪个更好?我通常编写后者,但我看到一些程序员像前者那样编写。

struct val_lessthan : binary_function<pair<string,int>, pair<string, int>, bool>
{
    bool operator() (const pair<string,int>& x, const pair<string,int>& y) const
    {
        return x.second < y.second;
    }
} val_lt;

bool val_lt(const pair<string,int>& x, const pair<string,int>& y) 
{
    return x.second < y.second;
}

我会像这样使用它:

std::sort(wordvector.begin(), wordvector.end(), val_lt);

4
做事最简单有效的方式是最好的。 - Bo Persson
你没有提到应该根据哪些标准来评判这些替代方案。有几种情况下,其中一种比另一种更好...但总的来说,选择简单的那个。如果考虑任何一种替代方案,请不要忘记C++1x的lambda表达式。 - Paul Michalik
可能是为什么要使用函数对象而不是函数?的重复问题。 - 463035818_is_not_a_number
6个回答

8
你看到有些人更喜欢第一种版本的原因是,函数对象可以轻松地内联。
当你将一个函数对象传递给std::sort时,函数知道函数对象类型,因此在编译时也知道要调用的确切函数,并且可以轻松地内联。
对于普通函数,std::sort实际上只看到一个函数指针,在编译时它无法了解该指针指向哪个函数。因此,除非编译器执行某些相当广泛的流分析来查看指针在这个特定调用中来自哪里,否则不能内联。虽然像你的小例子这样的简单情况下,编译器肯定会进行这种优化,但如果函数指针版本作为函数参数从其他地方传入,或者在传递给std::sort之前从中间数据结构中读取,则编译器可能无法内联函数指针版本,因此它会变慢。

6
第一个被称为“函数对象”,如果您需要将任何上下文信息传递给比较函数,则非常有用。独立函数只会得到参数x和y,没有机会携带任何上下文。
在上面的特定示例中,编写比较函数的两种方法大致等效。

1

通常情况下,我更倾向于使用第一种方法,但是我会更倾向于使用模板:

template <class T>
struct val_lessthan : binary_function<pair<pair<T, T>, bool> {
    bool operator()(T const &x, T const &y) const { 
       return x.second < y.second;
    }
};

使用.second会限制泛型程度,但你仍然能够得到一些好处(例如,如果我没记错,boost::tuple提供了一个对于两个元素的元组有.first.second)。通常情况下,作为一个模板能够更好地保证编译器将能够在内联中生成代码,所以如果你关心效率,这可能会有所帮助(或者不会,但不大可能会造成任何伤害)。


1
如果您想在代码的其他部分中也能够调用该函数,而不是将其作为一个函数对象传递,请选择函数形式。例如,您会更喜欢:
if (val_lt(a,b))
{
//...
}

if(val_lessthan()(a,b))
{
// ...
}

否则,在选择函数对象形式时,最好使用未命名的函数对象进行调用。也就是说:
std::sort(wordvector.begin(), wordvector.end(), val_lesstthan());

改为:

val_lesstthan named;
std::sort(wordvector.begin(), wordvector.end(), named);

取消命名参数和返回值可以轻松使编译器执行优化。这是指一个全局概念,称为RVO(返回值优化)。在这种情况下,它可能会免除您的代码从一次复制构造中。


Baumes:实际上,鉴于该对象是无状态的,编译器可能会完全省略构造过程。 - Matthieu M.
@Matthieu M. 谢谢,我完全没有考虑过这个。你是来自Sophia吗?我在那里学习过.. :-) - yves Baumes

0
我会说,选择最简单的适用于你特定情况的方法。在这种情况下,选择第二个而不是第一个。

-2

两者速度几乎相同,差别可以忽略不计。

当你使用函数对象时,这意味着在编译器生成的代码中,函数operator()有三个参数,第一个参数是指向val_lt对象本身的指针,第二个和第三个参数是你在签名中提到的参数。像这样:

//the possible code generated by the compiler!
bool operator() (val_lessthan *_this, const pair<string,int>& x, const pair<string,int>& y) const
              //^^^^^^^^^^^^^^^^^^^ note this!
{
    return x.second < y.second;
}

1
除了函数对象中的operator()是内联的,其余代码都可以正常运行。 - user102008

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