5 분 소요

모던 C++

함수자와 조건자

함수자는 함수처럼 동작하는 개체입니다. 함수는 함수명 + ()를 이용해서 호출하는데요, 함수자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); 

(C++11~) 이 추가되어 1회용 익명 함수를 만들 수 있습니다.

함수자에서 상태 활용

함수자는 개체이므로 상태를 멤버 변수로 저장하여 활용할 수 있습니다.

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()이 추가되었습니다. 인자(단항, 이항 제한이 없습니다.)로 전달한 함수자를 부정하는 함수자를 만듭니다.

댓글남기기