펑터 또는 펑크터라고 하는데, 여기선 펑터라고 하겠습니다.
많은 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
어댑터블 펑터와 함수 어댑터
앞서 말한 펑터 모두 순응성(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 << " ";
}
}
댓글