programing

왜 함수보다 펑터를 사용합니까?

javaba 2021. 1. 14. 23:03
반응형

왜 함수보다 펑터를 사용합니까?


비교

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

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

함수보다 펑터를 사용하면 어떤 이점이 있습니까? 첫 번째는 (구현이 추가되기 전에도) 읽기가 훨씬 더 쉽지 않습니까?

functor가 다음과 같이 정의되었다고 가정합니다.

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

최소한 네 가지 이유 :

우려의 분리

특정 예에서 functor 기반 접근 방식은 평균 계산 논리에서 반복 논리를 분리하는 이점이 있습니다. 따라서 다른 상황에서 펑터를 사용할 수 있으며 (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;
};

물론 기존 함수로 동일한 작업을 수행 할 수 있지만 .NET Framework와 다른 프로토 타입을 가지고 있기 때문에 함수 포인터와 함께 사용하기가 어렵습니다 CalculateAverage.

상태 저장

그리고 functor는 stateful이 될 수 있으므로 다음과 같이 할 수 있습니다.

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 참조 ).

공연

Functor는 종종 컴파일러에 의해 인라인 될 수 있습니다 (STL은 결국 템플릿 모음입니다). 이론적으로는 함수에 대해서도 마찬가지이지만 컴파일러는 일반적으로 함수 포인터를 통해 인라인하지 않습니다. 표준 예는 std::sortvs qsort; STL 버전은 비교 술어 자체가 단순하다고 가정하면 종종 5-10 배 더 빠릅니다.

요약

물론 기존 함수와 포인터로 처음 세 개를 에뮬레이트 할 수 있지만 펑터를 사용하면 훨씬 더 간단 해집니다.


Functor의 장점 :

  • Functions와 달리 Functor는 상태를 가질 수 있습니다.
  • Functor는 기능에 비해 OOP 패러다임에 적합합니다.
  • Functor는 종종 Function 포인터와 달리 인라인 될 수 있습니다.
  • Functor는 vtable 및 런타임 디스패치를 ​​필요로하지 않으므로 대부분의 경우 더 효율적입니다.

std::for_each쉽게 표준 알고리즘 중 가장 변덕스럽고 유용하지 않습니다. 루프를위한 멋진 래퍼입니다. 그러나 그것조차도 장점이 있습니다.

의 첫 번째 버전이 어떻게 생겼는지 고려하십시오 CalculateAverage. 반복자에 대한 루프가 있고 각 요소로 작업을 수행합니다. 루프를 잘못 작성하면 어떻게됩니까? 죄송합니다. 컴파일러 또는 런타임 오류가 있습니다. 두 번째 버전에는 이러한 오류가있을 수 없습니다. 예, 많은 코드는 아니지만 왜 루프를 그렇게 자주 작성해야합니까? 왜 한 번만?

이제 실제 알고리즘을 고려하십시오 . 실제로 작동하는 것. std::sort시겠습니까? 아니면 std::find? 아니면 std::nth_element? 가능한 가장 효율적인 방법으로 구현하는 방법을 알고 있습니까? 이러한 복잡한 알고리즘을 몇 번이나 구현 하시겠습니까?

읽기의 용이성은 보는 사람의 눈에 있습니다. 내가 말했듯이, std::for_each알고리즘에 대한 첫 번째 선택은 아닙니다 (특히 구문에 대한 C ++ 0x의 범위 기반). 하지만 실제 알고리즘에 대해 이야기하고 있다면 매우 읽기 쉽습니다. std::sort목록을 정렬합니다. 좀 더 모호한 것 중 일부는 std::nth_element익숙하지 않지만 언제든지 편리한 C ++ 참조에서 찾아 볼 수 있습니다.

C ++ 0x에서 Lambda를 사용하면 std :: for_each도 완벽하게 읽을 수 있습니다.


첫 번째 접근 방식에서 반복 코드는 컬렉션으로 무언가를 수행하려는 모든 함수에서 복제되어야합니다. 두 번째 방법은 반복의 세부 사항을 숨 깁니다.


• 함수와 달리 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; }
};

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

OOP는 여기서 키워드입니다.

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

4.1 Functor는 무엇입니까?

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.


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.

ReferenceURL : https://stackoverflow.com/questions/6451866/why-use-functors-over-functions

반응형