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

프로그래밍/C++ 2019.11.06 댓글 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 << " ";
       }
}

댓글