为什么使用函子而不是函数?

发布于 2024-11-17 01:40:28 字数 583 浏览 2 评论 0原文

比较

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

使用函子相

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

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

假设函子定义如下:

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;
   }
};

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;
   }
};

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

风苍溪 2024-11-24 01:40:28

至少有四个充分的理由:

关注点分离

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

参数化

您可以更轻松地参数化函子。例如,您可以有一个 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;
};

您当然可以用传统函数,但随后很难与函数指针一起使用,因为它与 CalculateAverage 具有不同的原型。

有状态

由于函子可以是有状态的,因此您可以执行以下操作:

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);

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

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

性能

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

总结

当然,可以使用传统函数和指针来模拟前三个,但使用函子会变得更加简单。

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.

Parameterisation

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

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.

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_each is a special case in this regard (see e.g. 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::sort vs qsort; the STL version is often 5-10x faster, assuming the comparison predicate itself is simple.

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.

帅哥哥的热头脑 2024-11-24 01:40:28

Functor 的优点:

  • 与 Function 不同,Functor 可以有状态。
  • 与函数相比,函子更适合 OOP 范式。
  • 与函数指针不同,Functor 通常可以内联,
  • Functor 不需要 vtable 和运行时调度,因此在大多数情况下更高效。

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.
拥抱我好吗 2024-11-24 01:40:28

std::for_each 无疑是标准算法中最反复无常且最无用的。它只是一个很好的循环包装。然而,即使它也有优点。

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

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

至于阅读的难易程度,那就是仁者见仁智者见智了。正如我所说,std::for_each 几乎不是算法的首选(尤其是使用 C++0x 基于范围的 for 语法)。但如果你谈论的是真正的算法,它们就非常具有可读性; std::sort 对列表进行排序。一些比较晦涩的内容(例如 std::nth_element)不会那么熟悉,但您始终可以在方便的 C++ 参考中查找它。

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

std::for_each is 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.

Consider what your first version of CalculateAverage must 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?

Now, consider real algorithms; 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?

As for ease of reading, that's in the eyes of the beholder. As I said, std::for_each is 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::sort sorts a list. Some of the more obscure ones like std::nth_element won't be as familiar, but you can always look it up in your handy C++ reference.

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

满天都是小星星 2024-11-24 01:40:28

•与函数不同,Functor 可以有状态。

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

这是 std::equal_to 的模板:

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

我可以想到 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";
}

因为所有 3 个解决方案都被编译进来,所以计数增加了 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

•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?

Here is template for 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:

#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:

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
瞎闹 2024-11-24 01:40:28

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

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.

如此安好 2024-11-24 01:40:28

OOP 是这里的关键字。

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

4.1 什么是函子?

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

OOP is keyword here.

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

4.1 What are Functors ?

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.

太阳男子 2024-11-24 01:40:28

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

您可以将 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)
}

或者您可以使用 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;
}

前者需要您了解更多的事情,但是一旦您了解了它们,就更简单并且留下的可能性更少错误。

它还只使用两个通用组件(std::accumulatestd::plus),在更复杂的情况下也经常出现这种情况。您通常可以拥有一个简单的通用仿函数(或函数;普通的旧函数可以充当仿函数),然后将其与您需要的任何算法简单地组合起来。

You are comparing functions on different level of abstraction.

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

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

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::accumulate and 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文