#16. [레거시 C++ STL] 함수자(Functor), 바인더, 어뎁터, 부정자
모던 C++
- (C++11~) 람다 표현식이 추가되어 1회용 익명 함수를 만들 수 있습니다.
- (C++11~) function이 추가되어
()
로 호출 가능한 함수자를 저장할 수 있습니다.- (C++11~) mem_fn()이 추가되었습니다. 인자가 있는 멤버 함수도 호출하는 함수자를 좀 더 간편하게 만들어 줍니다.
- (C++11~) reference_wrapper, ref(), cref()는 복사 생성이나 복사 대입이 안되는 참조자를 래핑합니다.
- (C++11~) bind()가 추가되어
placeholders::_1
(GCC의 경우_1
,_2
,_3
, …_29
가 정의됨)와 같은 자리 표시자와 조합하여 특정 인자만을 사용하는 함수자를 생성할 수 있습니다.- (C++11~) 함수자 타입 특성 클래스(unary_function, binary_function등), 바인더(bind1st(), bind2nd()등), 어뎁터와 부정자(mem_fun(), mem_fun_ref(), ptr_fun(), not1(), not2()등)가 람다 표현식, function, bind(), mem_fn()등으로 대체되어 deprecate 되었습니다.
- (C++11~) hash()가 추가되어 각 타입별로 데이터의 해시값(Digest)을 구할 수 있습니다.
- (C++17~) not_fn()이 추가되어 인자(단항, 이항 제한이 없습니다.)로 전달한 함수자를 부정하는 함수자를 만듭니다.
함수자와 조건자
함수자는 함수처럼 동작하는 개체입니다. 함수는 함수명 + ()
를 이용해서 호출하는데요, 함수자는 operator ()
을 재구현한 개체로서 함수와 동일하게 개체명 + ()
로 호출할 수 있습니다.(특별히 조건 검사를 위해 bool을 리턴하면 조건자(Predicate) 라고도 합니다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
template<typename InputIterator, typename Function>
Function my_for_each(InputIterator first, InputIterator last, Function f) {
for (; first != last; ++first) {
f(*first); // 함수명 + () 또는 개체명 + () 로 호출됩니다.
}
return f;
}
// 10을 세팅하는 함수입니다.
void Func(int& val) {
val = 10;
}
// 20을 세팅하는 함수자입니다.
class Functor {
public:
void operator ()(int& val) {
val = 20;
}
};
// 0으로 초기화 되어 생성
std::vector<int> v(3);
// 함수 호출
my_for_each(v.begin(), v.end(), Func);
EXPECT_TRUE(v[0] == 10 && v[1] == 10 && v[2] == 10);
// 함수자 호출
my_for_each(v.begin(), v.end(), Functor());
EXPECT_TRUE(v[0] == 20 && v[1] == 20 && v[2] == 20);
함수자에서 상태 활용
함수자는 개체이므로 상태를 멤버 변수로 저장하여 활용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 각 요소에 지정한 값을 추가합니다.
class Adder {
int m_Val; // 상태를 저장합니다.
public:
explicit Adder(int val) : m_Val(val) {}
void operator ()(int& val) {
val += m_Val;
}
};
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// v 의 각 요소에 10을 더합니다.
Adder adder(10);
std::for_each(v.begin(), v.end(), adder);
EXPECT_TRUE(v[0] == 11 && v[1] == 12 && v[2] == 13);
함수자 타입 특성 클래스(Traits)
함수자 정의시 표준적인 사용을 위해 다음의 타입 특성 클래스를 사용하는게 좋습니다.
항목 | 내용 |
---|---|
unary_function |
단항 함수자 |
binary_function |
이항 함수자 |
1
2
3
4
5
6
7
8
9
10
11
template<typename Arg, typename Res>
struct unary_function {
typedef Arg argument_type;
typedef Res result_type;
};
template<typename Arg, typename Arg2, typename Res>
struct binary_function {
typedef Arg first_argument_type;
typedef Arg2 second_argument_type;
typedef Res result_type;
};
Adder
함수자는 다음처럼 unary_function을 상속하여 구현할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 인자 1개를 사용하는 함수자 정의
class Adder : std::unary_function<int&, void> {
argument_type m_Val;
public:
explicit Adder(argument_type val) : m_Val(val) {}
result_type operator ()(argument_type val) {
val += m_Val;
}
};
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
// v 의 각 요소에 10을 더합니다.
int init = 10; // int& 타입이어서 변수로 값을 전달합니다.
Adder adder(init);
std::for_each(v.begin(), v.end(), adder);
EXPECT_TRUE(v[0] == 11 && v[1] == 12 && v[2] == 13);
(C++11~) 함수자 타입 특성 클래스(unary_function, binary_function등)는 deprecate 되었습니다. function을 사용합니다.
STL 기본 함수자
다음과 같은 기본 함수자가 제공됩니다. 전체 내용은 모던 C++의 함수자를 참고하시기 바랍니다.
항목 | 내용 |
---|---|
equal_to |
arg1 == arg2 |
not_equal_to |
arg1 != arg2 |
greater |
arg1 > arg2 |
less |
arg1 < arg2 |
greater_equal |
arg1 >= arg2 |
less_equal |
arg1 <= arg2 |
logical_and |
arg1 == arg2 |
logical_or |
arg1 || arg2 |
logical_not |
!arg |
plus |
arg1 + arg2 |
minus |
arg1 - arg2 |
multiplies |
arg1 * arg2 |
divides |
arg1 / arg2 |
modulus |
arg1 % arg2 |
negate |
-arg |
바인더
이항 연산이 필요한 함수를 단항 함수처럼 바꿔주는 개체입니다.
항목 | 내용 |
---|---|
bind1st(op, x) |
알고리즘엔 단항 함수로 전달되며, op(x, 요소) 로 이항 함수를 호출합니다. |
bind2nd(op, y) |
알고리즘엔 단항 함수로 전달되며, op(요소, y) 로 이항 함수를 호출합니다. |
예를 들어 vector에서 7
보다 작은 첫 요소를 찾는다고 가정해 봅시다. find_if()
를 사용하려고 봤더니 단항 함수자인 조건자(Predicate)를 사용합니다.
1
2
3
4
5
6
7
8
9
10
11
12
template<typename InputIterator, typename Predicate>
InputIterator my_find_if( // std 버전도 대략 이렇게 구현되었습니다.
InputIterator first,
InputIterator last,
Predicate pred) {
// 조건자(Predicate)가 참이면 해당 위치의 이터레이터를 리턴합니다.
while (first != last && !pred(*first)) {
++first;
}
return first;
}
따라서 하기와 같이 단항 함수자를 만들어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 7보다 작은지 검사하는 함수
class Less_7 : std::unary_function<int, bool> {
public:
result_type operator ()(argument_type val) {
return val < 7 ? true : false;
}
};
std::vector<int> v;
v.push_back(7);
v.push_back(4);
v.push_back(3);
std::vector<int>::iterator itr = my_find_if(
v.begin(),
v.end(),
Less_7()
);
EXPECT_TRUE(*itr == 4);
이렇게 매번 함수자를 새로 만들면 번거로우므로, 기존에 표준에서 제공한 이항 함수자인 less<int>()
를 다음처럼 bind2nd(op, y)
를 이용하여 단항 함수자로 만들 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::vector<int> v;
v.push_back(7);
v.push_back(4);
v.push_back(3);
// less(x, y)는 x < y 를 통해 true/false를 리턴하는 이항 함수자
// bind2nd()를 통해 알고리즘에서 less(벡터 요소, 7) 로 호출됨
// 7보다 작은 첫 요소를 리턴함
std::vector<int>::iterator itr = std::find_if(
v.begin(),
v.end(),
std::bind2nd(std::less<int>(), 7) // 7보다 작으면 true를 리턴하는 단항 함수자
);
EXPECT_TRUE(*itr == 4);
(C++11~) 바인더(bind1st(), bind2nd()등)은 deprecate 되었습니다.
(C++11~) bind()가 추가되어placeholders::_1
(GCC의 경우_1
,_2
,_3
, …_29
가 정의됨)와 같은 자리 표시자와 조합하여 특정 인자만을 사용하는 함수자를 생성할 수 있습니다.
어뎁터와 부정자
알고리즘에서는 함수 호출시 f(컨테이너 요소)
의 형태로 호출합니다. 따라서 요소.f()
형태로 호출해야 하는 요소의 멤버 함수는 호출 할 수 없습니다.
그래서 컨테이너 요소들의 멤버 함수를 호출해야 하는 경우 다음과 같이 알고리즘을 이용하지 않고, 이터레이터를 이용하여 멤버 함수를 호출할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
class A {
public:
void Func() {}
};
std::vector<A> v(2);
std::vector<A>::iterator itr = v.begin();
std::vector<A>::iterator endItr = v.begin();
for (; itr != endItr; ++itr) {
(*itr).Func();
}
이런 경우, 하기와 같은 어뎁터를 이용하면 알고리즘에서도 멤버 함수를 호출할 수 있습니다.
항목 | 내용 |
---|---|
mem_fun() |
알고리즘에서 f(x) 의 호출을 x->f() 처럼 호출되게 합니다. |
mem_fun_ref() |
알고리즘에서 f(x) 의 호출을 x.f() 처럼 호출되게 합니다. |
ptr_fun() |
바인더, 어뎁터, 부정자와 일반 함수가 호환될 수 있도록 일반 함수를 unary_function 이나 binary_function 개체로 만듭니다. |
not1() |
단항 조건자의 리턴값을 부정합니다. |
not2() |
이항 조건자의 리턴값을 부정합니다. |
상기 코드는 for_each()와 mem_fun_ref()를 이용하여 다음처럼 좀 더 간결하게 만들 수 있습니다.
1
2
3
4
5
6
7
8
9
10
class A {
public:
void Func() {}
};
std::vector<A> v(2);
std::for_each(
v.begin(), v.end(),
std::mem_fun_ref(&A::Func) // 요소.Func()으로 호출해 주는 어뎁터입니다.
);
(C++11~) 어뎁터와 부정자(mem_fun(), mem_fun_ref(), ptr_fun(), not1(), not2()등)는 deprecate 되었습니다.
(C++11~) mem_fn()을 이용하여 멤버 함수를 호출합니다.
(C++17~) not_fn()이 추가되었습니다. 인자(단항, 이항 제한이 없습니다.)로 전달한 함수자를 부정하는 함수자를 만듭니다.
댓글남기기