C++ 为什么在函数上使用函子?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6451866/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 20:10:37  来源:igfitidea点击:

Why use functors over functions?

c++stlfunctor

提问by DanDan

Compare

相比

double average = CalculateAverage(values.begin(), values.end());

with

double average = std::for_each(values.begin(), values.end(), CalculateAverage());

What are the benefits of using a functor over a function? Isn't the first a lot easier to read (even before the implementation is added)?

在函数上使用函子有什么好处?第一个是不是更容易阅读(甚至在添加实现之前)?

Assume the functor is defined like this:

假设函子定义如下:

class CalculateAverage
{
private:
   std::size_t num;
   double sum;
public:

   CalculateAverage() : num (0) , sum (0)
   {
   }

   void operator () (double elem) 
   {
      num++; 
      sum += elem;
   }

   operator double() const
   {
       return sum / num;
   }
};

回答by Oliver Charlesworth

At least four good reasons:

至少有四个很好的理由:

Separation of concerns

关注点分离

In your particular example, the functor-based approach has the advantage of separating the iteration logic from the average-calculation logic. So you can use your functor in other situations (think about all the other algorithms in the STL), and you can use other functors with for_each.

在您的特定示例中,基于函子的方法具有将迭代逻辑与平均计算逻辑分开的优点。所以你可以在其他情况下使用你的函子(想想 STL 中的所有其他算法),你可以使用其他函子和for_each.

Parameterisation

参数化

You can parameterise a functor more easily. So for instance, you could have a CalculateAverageOfPowersfunctor that takes the average of the squares, or cubes, etc. of your data, which would be written thus:

您可以更轻松地参数化函子。因此,例如,您可以有一个CalculateAverageOfPowers函子,它取数据的平方或立方体等的平均值,可以这样写:

class CalculateAverageOfPowers
{
public:
    CalculateAverageOfPowers(float p) : acc(0), n(0), p(p) {}
    void operator() (float x) { acc += pow(x, p); n++; }
    float getAverage() const { return acc / n; }
private:
    float acc;
    int   n;
    float p;
};

You could of course do the same thing with a traditional function, but then makes it difficult to use with function pointers, because it has a different prototype to CalculateAverage.

您当然可以对传统函数做同样的事情,但是这样就很难与函数指针一起使用,因为它与CalculateAverage.

Statefulness

有状态

And as functors can be stateful, you could do something like this:

由于函子可以是有状态的,你可以这样做:

CalculateAverage avg;
avg = std::for_each(dataA.begin(), dataA.end(), avg);
avg = std::for_each(dataB.begin(), dataB.end(), avg);
avg = std::for_each(dataC.begin(), dataC.end(), avg);

to average across a number of different data-sets.

对许多不同的数据集进行平均。

Note that almost all STL algorithms/containers that accept functors require them to be "pure" predicates, i.e. have no observable change in state over time. for_eachis a special case in this regard (see e.g. Effective Standard C++ Library - for_each vs. transform).

请注意,几乎所有接受函子的 STL 算法/容器都要求它们是“纯”谓词,即随着时间的推移没有可观察到的状态变化。 for_each在这方面是一个特例(参见例如Effective Standard C++ Library - for_each vs. transform)。

Performance

表现

Functors can often be inlined by the compiler (the STL is a bunch of templates, after all). Whilst the same is theoretically true of functions, compilers typically won't inline through a function pointer. The canonical example is to compare std::sortvs qsort; the STL version is often 5-10x faster, assuming the comparison predicate itself is simple.

函子通常可以由编译器内联(毕竟,STL 是一堆模板)。虽然理论上函数也是如此,但编译器通常不会通过函数指针内联。典型的例子是比较std::sortvs qsort; STL 版本通常快 5-10 倍,假设比较谓词本身很简单。

Summary

概括

Of course, it's possible to emulate the first three with traditional functions and pointers, but it becomes a great deal simpler with functors.

当然,可以用传统的函数和指针来模拟前三个,但使用函子就变得简单多了。

回答by Alok Save

Advantages of Functors:

函子的优点:

  • Unlike Functions Functor can have state.
  • Functor fits into OOP paradigm as compared to functions.
  • Functor often may be inlined unlike Function pointers
  • Functor doesn't require vtable and runtime dispatching, and hence more efficient in most cases.
  • 不像函数,函子可以有状态。
  • 与函数相比,Functor 适合 OOP 范式。
  • 与函数指针不同,函子通常可以被内联
  • Functor 不需要 vtable 和运行时分派,因此在大多数情况下效率更高。

回答by Nicol Bolas

std::for_eachis easily the most capricious and least useful of the standard algorithms. It's just a nice wrapper for a loop. However, even it has advantages.

std::for_each很容易成为标准算法中最反复无常和最没用的。它只是一个很好的循环包装器。然而,即使它也有优势。

Consider what your first version of CalculateAveragemust look like. It will have a loop over the iterators, and then do stuff with each element. What happens if you write that loop incorrectly? Oops; there's a compiler or runtime error. The second version can never have such errors. Yes, it's not a lot of code, but why do we have to write loops so often? Why not just once?

考虑一下您的第一个版本CalculateAverage必须是什么样子。它将对迭代器进行循环,然后对每个元素进行处理。如果您错误地编写该循环会发生什么?哎呀;存在编译器或运行时错误。第二个版本永远不会有这样的错误。是的,代码不是很多,但是为什么我们要这么频繁地写循环呢?为什么不是一次?

Now, consider realalgorithms; the ones that actually do work. Do you want to write std::sort? Or std::find? Or std::nth_element? Do you even know how to implement it in the most efficient way possible? How many times do you want to implement these complex algorithms?

现在,考虑真正的算法;那些真正起作用的。你想写std::sort吗?或者std::find?或者std::nth_element?您甚至知道如何以最有效的方式实施它吗?你想实现多少次这些复杂的算法?

As for ease of reading, that's in the eyes of the beholder. As I said, std::for_eachis hardly the first choice for algorithms (especially with C++0x's range-based for syntax). But if you're talking about real algorithms, they're very readable; std::sortsorts a list. Some of the more obscure ones like std::nth_elementwon't be as familiar, but you can always look it up in your handy C++ reference.

至于阅读的方便性,那是旁观者的眼睛。正如我所说,std::for_each几乎不是算法的首选(尤其是 C++0x 的基于范围的语法)。但是如果你在谈论真正的算法,它们是非常易读的;std::sort对列表进行排序。一些更晦涩的像std::nth_element不会那么熟悉,但您总是可以在方便的 C++ 参考中查找它。

And even std::for_each is perfectly readable once you use Lambda's in C++0x.

一旦您在 C++0x 中使用 Lambda,甚至 std::for_each 也是完全可读的。

回答by joe

?Unlike Functions Functor can have state.

? 不像函数,函子可以有状态。

This is very interesting because std::binary_function, std::less and std::equal_to has a template for an operator() that is const. But what if you wanted to print a debug message with the current call count for that object, how would you do it?

这很有趣,因为 std::binary_function、std::less 和 std::equal_to 有一个用于 const 的 operator() 的模板。但是,如果您想使用该对象的当前调用计数打印一条调试消息,您会怎么做呢?

Here is template for std::equal_to:

这是 std::equal_to 的模板:

struct equal_to : public binary_function<_Tp, _Tp, bool>
{
  bool
  operator()(const _Tp& __x, const _Tp& __y) const
  { return __x == __y; }
};

I can think of 3 ways to allow the operator() to be const, and yet change a member variable. But what is the best way? Take this example:

我可以想到 3 种方法来允许 operator() 为 const,但更改成员变量。但最好的方法是什么?拿这个例子:

#include <iostream>
#include <string>
#include <algorithm>
#include <functional>
#include <cassert>  // assert() MACRO

// functor for comparing two integer's, the quotient when integer division by 10.
// So 50..59 are same, and 60..69 are same.
// Used by std::sort()

struct lessThanByTen: public std::less<int>
{
private:
    // data members
    int count;  // nr of times operator() was called

public:
    // default CTOR sets count to 0
    lessThanByTen() :
        count(0)
    {
    }


    // @override the bool operator() in std::less<int> which simply compares two integers
    bool operator() ( const int& arg1, const int& arg2) const
    {
        // this won't compile, because a const method cannot change a member variable (count)
//      ++count;


        // Solution 1. this trick allows the const method to change a member variable
        ++(*(int*)&count);

        // Solution 2. this trick also fools the compilers, but is a lot uglier to decipher
        ++(*(const_cast<int*>(&count)));

        // Solution 3. a third way to do same thing:
        {
        // first, stack copy gets bumped count member variable
        int incCount = count+1;

        const int *iptr = &count;

        // this is now the same as ++count
        *(const_cast<int*>(iptr)) = incCount;
        }

        std::cout << "DEBUG: operator() called " << count << " times.\n";

        return (arg1/10) < (arg2/10);
    }
};

void test1();
void printArray( const std::string msg, const int nums[], const size_t ASIZE);

int main()
{
    test1();
    return 0;
}

void test1()
{
    // unsorted numbers
    int inums[] = {33, 20, 10, 21, 30, 31, 32, 22, };

    printArray( "BEFORE SORT", inums, 8 );

    // sort by quotient of integer division by 10
    std::sort( inums, inums+8, lessThanByTen() );

    printArray( "AFTER  SORT", inums, 8 );

}

//! @param msg can be "this is a const string" or a std::string because of implicit string(const char *) conversion.
//! print "msg: 1,2,3,...N", where 1..8 are numbers in nums[] array

void printArray( const std::string msg, const int nums[], const size_t ASIZE)
{
    std::cout << msg << ": ";
    for (size_t inx = 0; inx < ASIZE; ++inx)
    {
        if (inx > 0)
            std::cout << ",";
        std::cout << nums[inx];
    }
    std::cout << "\n";
}

Because all 3 solutions are compiled in, it increments count by 3. Here's the output:

因为所有 3 个解决方案都被编译进来,所以它会将 count 增加 3。这是输出:

gcc -g -c Main9.cpp
gcc -g Main9.o -o Main9 -lstdc++
./Main9
BEFORE SORT: 33,20,10,21,30,31,32,22
DEBUG: operator() called 3 times.
DEBUG: operator() called 6 times.
DEBUG: operator() called 9 times.
DEBUG: operator() called 12 times.
DEBUG: operator() called 15 times.
DEBUG: operator() called 12 times.
DEBUG: operator() called 15 times.
DEBUG: operator() called 15 times.
DEBUG: operator() called 18 times.
DEBUG: operator() called 18 times.
DEBUG: operator() called 21 times.
DEBUG: operator() called 21 times.
DEBUG: operator() called 24 times.
DEBUG: operator() called 27 times.
DEBUG: operator() called 30 times.
DEBUG: operator() called 33 times.
DEBUG: operator() called 36 times.
AFTER  SORT: 10,20,21,22,33,30,31,32

回答by Vijay Mathew

In the first approach the iteration code has to be duplicated in all functions that wants to do something with the collection. The second approach hide the details of iteration.

在第一种方法中,迭代代码必须在所有想要对集合做某事的函数中复制。第二种方法隐藏迭代的细节。

回答by Ярослав Рахматуллин

OOP is keyword here.

OOP 是这里的关键字。

http://www.newty.de/fpt/functor.html:

http://www.newty.de/fpt/functor.html

4.1 What are Functors ?

4.1 什么是函子?

Functors are functions with a state. In C++ you can realize them as a class with one or more private members to store the state and with an overloaded operator () to execute the function. Functors can encapsulate C and C++ function pointers employing the concepts templates and polymorphism. You can build up a list of pointers to member functions of arbitrary classes and call them all through the same interface without bothering about their class or the need of a pointer to an instance. All the functions just have got to have the same return-type and calling parameters. Sometimes functors are also known as closures. You can also use functors to implement callbacks.

函子是具有状态的函数。在 C++ 中,您可以将它们实现为具有一个或多个私有成员来存储状态并使用重载运算符 () 来执行函数的类。函子可以使用模板和多态性概念封装 C 和 C++ 函数指针。您可以构建一个指向任意类成员函数的指针列表,并通过相同的接口调用它们,而无需担心它们的类或需要指向实例的指针。所有函数都必须具有相同的返回类型和调用参数。有时函子也被称为闭包。您还可以使用函子来实现回调。

回答by Jan Hudec

You are comparing functions on different level of abstraction.

您正在比较不同抽象级别的函数。

You can implement CalculateAverage(begin, end)either as:

您可以实现CalculateAverage(begin, end)为:

template<typename Iter>
double CalculateAverage(Iter begin, Iter end)
{
    return std::accumulate(begin, end, 0.0, std::plus<double>) / std::distance(begin, end)
}

or you can do it with a for loop

或者你可以用 for 循环来做

template<typename Iter>
double CalculateAverage(Iter begin, Iter end)
{
    double sum = 0;
    int count = 0;
    for(; begin != end; ++begin) {
        sum += *begin;
        ++count;
    }
    return sum / count;
}

The former requires you to know more things, but once you know them, is simpler and leaves fewer possibilities for error.

前者要求你知道更多的东西,但一旦你知道它们,就更简单,出错的可能性也更少。

It also only uses two generic components (std::accumulateand std::plus), which is often the case in more complex case too. You can often have a simple, universal functor (or function; plain old function can act as functor) and simply combine it with whatever algorithm you need.

它也只使用两个通用组件(std::accumulatestd::plus),这在更复杂的情况下也是如此。你通常可以有一个简单的、通用的函子(或函数;普通的旧函数可以充当函子),然后简单地将它与你需要的任何算法结合起来。