플로렌스라는 개발자

C++ 펑터 또는 펑크터(functor)

프로그래밍/C++ 2019.11.06 댓글 0 Plorence

목차

    펑터 또는 펑크터라고 하는데, 여기선 펑터라고 하겠습니다.

    많은 STL 알고리즘들이 펑터(Functor)라고 부르는 함수 객체(Function object)를 많이 사용합니다.

    펑터는 함수처럼 ()과 함께 사용할 수 있는 객체입니다.

    일반 함수의 이름, 함수를 지시하는 포인터, () 연산자가 오버로딩된 클래스 객체 모두 펑터가 될 수 있습니다.

    #include <iostream>
    class Money {
    private:
           int _Money = 0;
    public:
           int operator()() {
                  return this->_Money;
           }
           void operator()(int N) {
                  this->_Money += N;
           }
    };
    int main(void) {
           Money money;
           money(100); //void operator()(int)
           int M = money(); //int operator()()
           std::cout << M;
    }
    100

    오버로딩된 () 연산자가 Money 객체들을 함수처럼 사용하는 것을 허용합니다.

     

    펑터 개념

    STL은 컨테이너와 이터레이터의 개념을 정의하듯이, 펑터의 개념도 정의합니다.

    • 제너레이터(generator)는 매개변수 없이 호출하는 함수입니다.

    • 단항 함수(unary function)는 하나의 매개변수로 호출하는 함수입니다.

    • 이항 함수(binary function)는 두 개의 매개변수로 호출하는 함수입니다.

    이러한 개념들도 개량을 가질 수 있습니다.

    • bool 값을 리턴하는 단항 함수는 조건(predicate)이다.

    • bool 값을 리턴하는 이항 함수는 이항 조건(binary predicate)이다.

     

    몇 개의 STL 함수들은 조건 또는 이항 조건요구하는 함수가 있습니다.

    대표적으로 sort함수입니다. (sort함수는 이항 조건 제3의 매개변수로 취하는 버전이 있음)

     

    예시

    #include <iostream>
    #include <algorithm>
    bool Compare(int N1, int N2) {
           if (N1 < N2) {
                  return true;
           }
           else {
                  return false;
           }
    }
    int main(void) {
           int Array[] = { 5,3,2,1,7,8 };
           std::sort(Array, Array + 6, Compare);
    }

    Compare함수가 반환하는 값(true 또는 false)에 따라 정렬이 됩니다.

    즉 디폴트로 사용된 정렬 기준 말고, 사용자가 직접 만든 정렬 기준을 사용해 정렬합니다.

     

    또 다른 예시로는 list 컨테이너에서 원소의 값이 10이상이면 삭제하고 싶다고 가정합시다.

    이때 삭제의 기준이 되는 값을 Compare에 전달할 수 있으면 좋을겁니다.

    (또 다른 기준을 만들려면 그만큼 함수가 더 많이 필요함.)

    그러나 조건은 하나의 매개변수만 받아들일 수 있습니다.

    그래서 함수 매개변수 대신 클래스 멤버를 사용하여 추가 정보를 전달할 수 있습니다.

     

    예시

    #include <iostream>
    #include <list>
    template<class T>
    class Compare {
    private:
           T _Standard;
    public:
           Compare(const T & N) : _Standard(N) {
           }
           bool operator()(const T & N) {
                  return N >= _Standard;
           }
    };
    int main(void) {
           std::list<int> list = std::list<int>();
           list.push_back(10);
           list.push_back(100);
           list.push_back(5);
           list.push_back(4);
           list.push_back(70);
           list.push_back(20);
           list.remove_if(Compare<int>(10));
           for (auto i : list) {
                  std::cout << i << std::endl;
           }
    }
    5
    4

    여기서 생성자에서 매개변수 N은 객체가 선언될 때 들어가는 삭제의 기준이 되는 값이고

    펑터의 매개변수 N은 remove_if함수로 인해 원소의 값을 전달받아 넣어 삭제 할 것인지 판단합니다.

     

    또는 템플릿 함수와 템플릿 클래스를  섞어 사용하여도 가능합니다.

    #include <iostream>
    #include <list>
    template<class T>
    bool Compare(const T & N, const T & Standard) {
           return N >= Standard;
    }
    template<class T>
    class Compare2 {
    private:
           T _Standard;
    public:
           Compare2(const T & N) : _Standard(N) {
           }
           bool operator()(const T & N) {
                  return Compare<T>(N, _Standard);
           }
    };
    int main(void) {
           Compare2<int> compare(100);
           std::cout << compare(1000);
    }
    1

    이때 두 개의 매개변수를 사용하는 함수가 하나의 매개변수를 사용하는 함수 객체변환되었습니다.

    결국엔 클래스 펑터 Compare2는 함수 어댑터로서, 함수를 다른 인터페이스에 맞게 개조시켰습니다.

     

    미리 정의된 펑터

    STL은 몇 가지 기본적인 펑터들을 정의하였습니다.

    두 값을 더하는 것, 두 값이 같은지, 또는 두 값에 대한 비교 같은 연산을 수행합니다.

    (functional 헤더 파일에 존재함.)

    예를 들어 내림차순으로 정렬한다고 하면, greater펑터를 사용해야 합니다.

    #include <iostream>
    #include <vector>
    #include <functional>
    #include <algorithm>
    int main(void) {
    	std::vector<int> vector = std::vector<int>();
    	vector.push_back(100);
    	vector.push_back(1000);
    	vector.push_back(1020);
    	vector.push_back(500);
    	std::sort(vector.begin(), vector.end(), std::greater<int>());
    	for (auto i : vector) {
    		std::cout << i << std::endl;
    	}
    }
    1020
    1000
    500
    100

    아래는 연산자와 동등한 펑터를 표로 정리한 것입니다.

    연산자

    펑터

    +

    plus

    -

    minus

    *

    multiplies (구형 C++ 시스템에서는 times)

    /

    divides

    %

    modulus

    -

    negate

    ==

    equals_to

    !=

    not_equals_to

    >

    greater

    <

    less

    >=

    greater_equal

    <=

    less_equal

    &&

    logical_and

    !!

    logical_or

    !

    logical_not

    연산자를 보면 어떤 게 단항 함수, 이항 함수, 조건, 이항 조건인지 아실 겁니다.

    negate는 부정을 수행합니다.

    https://en.cppreference.com/w/cpp/utility/functional/negate

     

    std::negate - cppreference.com

    template< class T > struct negate; (until C++14) template< class T = void > struct negate; (since C++14) Function object for performing negation. Effectively calls operator- on an instance of type T. [edit] Specializations The standard library provides a s

    en.cppreference.com

     

    어댑터블 펑터와 함수 어댑터

    앞서 말한 펑터 모두 순응성(adaptable)입니다.

    STL에는 

    • 순응성 제너레이터(adaptable generator)

    • 순응성 단항 함수(adaptable unary function)

    • 순응성 이항 함수(adaptable binary function)

    • 순응성 조건(adaptable predicate)

    • 순응성 이항 조건(adaptable binary predicate)

    총 다섯 가지 개념이 있습니다.

     

    펑터를 순응성으로 만드는 것은 매개변수형과 리턴형을 식별하는 typedef 멤버들을 가지는 것입니다.

    그 멤버들은 result_type, first_argument_type, second_argument_type 등인데, 이름이 암시하는 것을 나타냅니다.

     

    펑터가 순응성이라는 것은, 이러한 typedef 멤버들의 존재를 가정하는 함수 어댑터 객체에 사용할 수 있다는 것입니다.

    예를 들어, 순응성 펑터를 매개변수를 취하는 함수는, result_type 멤버를 사용하여 그 함수의 리턴형과 일치하는 변수를 선언할 수 있습니다.

     

    STL은 이러한 기능들을 사용하는 함수 어댑터 클래스를 제공합니다.

    예를 들어 vector의 각 원소들을 * 2 한다고 가정합시다.

    그러면 단항 함수 매개변수를 받아들이는 transform() 버전을 사용해야 합니다.

     

    하지만 여기서 문제점은, 미리 정의된 펑터중에 곱하는 펑터인 multiplies는 이항 함수입니다.

    따라서 두 개의 매개변수를 사용하는 펑터를 하나의 매개변수를 사용하는 펑터로 변환하는 함수 어댑터가 필요합니다.

     

    binder1st 클래스 사용

    #include <iostream>
    #include <algorithm>
    #include <functional>
    #include <vector>
    int main(void) {
           std::vector<int> vec;
           vec.push_back(10);
           vec.push_back(20);
           std::binder1st<std::multiplies<int> > b1 =  std::binder1st<std::multiplies<int> >(std::multiplies<int>(), 2);
           std::transform(vec.begin(), vec.end(), vec.begin(), b1);
           for (auto i : vec) {
                  std::cout << i << " ";
           }
    }

    binder1st 생성자 첫 번째 매개변수가 순응성 이항 함수, 두 번째 매개변수가 이항 함수 첫 번째 매개변수로 사용할 값입니다.

    이때 생성자 첫 번째 매개변수가 순응성 함수일 경우에만 사용이 가능합니다.

    binder2nd도 있는데, 이건 두 번째 매개변수가 이항 함수 두 번째 매개변수로 사용할 값(즉 고정값)이 됩니다.

    #include <iostream>
    #include <algorithm>
    #include <functional>
    #include <vector>
    int main(void) {
           std::vector<int> vec;
           vec.push_back(10);
           vec.push_back(20);
           std::binder1st<std::multiplies<int> > b1 =  std::binder1st<std::multiplies<int> >(std::multiplies<int>(), 2);    
           for (auto i : vec) {
                  std::cout << b1(i) << " ";
           }
    }

    함수 객체니까 이렇게 따로 함수처럼 사용하는 것도 가능합니다.

     

    bind1st 함수 사용

    STL은 binder1st 클래스 사용을 간소화하는 bind1st() 함수를 제공합니다.

    마찬가지로 bind2nd() 함수도 있습니다.

    binder1st 객체를 생성하는 데 사용할 함수 이름과 값을 bind1st 함수에 제공하면 그 형의 객체가 리턴됩니다.

    #include <iostream>
    #include <algorithm>
    #include <functional>
    #include <vector>
    int main(void) {
           std::vector<int> vec;
           vec.push_back(10);
           vec.push_back(20);
           std::transform(vec.begin(), vec.end(), vec.begin(),  std::bind1st(std::multiplies<int>(), 2));
           for (auto i : vec) {
                  std::cout << i << " ";
           }
    }

    댓글

    0