반응형

http://blog.daum.net/coolprogramming/90

 1, 함수자

operator() 연산자를 중복하고 있는 클래스의 객체를 함수 객체( function object) 혹은 함수자(functor)라 합니다.

그래서 함수자는 함수처럼 사용할 수 있습니다.

함수자는 02. C++, STL QS2의 함수 포인터와 함수자를 참고하세요.

 

아래는 간단한 세가지 함수류(함수, 함수 포인터, 함수자)를 사용한 예제 코드입니다.

 

1 함수, 2 함수 포인터, 3 함수자를 사용한  두 정수의 합 예제입니다.

#include <iostream>
using namespace std;

int Plus(int a, int b)
{
    return a+b;
}
struct CPlus
{
    int operator( )(int a, int b)
    {
        return a+b;
    }
};
void main( )
{
    int result;
    int (*pPlus)(intint) = Plus;
    CPlus oPlus;

    result = Plus( 10 , 20)// 1. 함수 호출
    cout << result << endl;
   
    result = pPlus( 10, 20)// 2. 함수 포인터로 호출
    cout << result << endl;

    result = oPlus( 10, 20)// 3. 함수자로 호출
    cout << result << endl;
}
  1. 30
    30
    30

 위 예제를 보면 함수, 함수 포인터, 함수자의 인터페이스는 모두 같다는 것을 알 수 있습니다.

 

함수자를 사용하면 아래와 같은 장점이 있습니다.

  • 컴파일 시에 함수자를 알고리즘의 인자로 전달할 수 있다.
  • 함수 호출을 인라인(inline)화해서 효율성을 향상시킬 수 있다.
  • 함수자의 정보를 캡슐화하여 지역화(locally)할 수 있다.

 

 2, STL 함수자

STL 함수자는 크게 두 가지로 나눌 수 있습니다.

  • 1, 일반 함수자 : 특정 기능을 수행하는 함수자.

    • 산술 연산 함수자 : 산술 연산 기능을 수행.
    • 비교 연산 함수자 : 비교 연산 기능을 수행.
    • 논리 연산 함수자 : 논리 연산 기능을 수행.

       

  • 2, 함수 어댑터 : 함수류(함수와, 함수 포인터, 함수자)를 인자로 받아 새로운 함수자를 생성.

    • 바인더(binder) : 이항 함수자를 단항 함수자로 변환하는 함수자.
    • 부정자(negator) : 조건자(predicate)를 반대로 변환하는 함수자.
    • 함수 포인터 어댑터(adaptors for pointersto functions) : 함수 포인터를 라이브러리가 필요로 하는 함수자로 변환하는 함수자
    • 멤버 함수 포인터 어댑터(adaptors for pointers to member functions) : 멤버 함수 포인터를 라이브러리가 필요로 하는 함수자로 변환하는 함수자

어댑터의 인자로 사용되는 함수자는 아래 요구 사항을 지켜야 합니다.

  • 단항 함수자는 반드시 argument_type, result_type이 정의되어 있어야 한다. 각각은 함수의 인자 타입과 리턴 타입이다.
  • 이항 함수자는 반드시 first_argument_type, second_argument_type, result_type이 정의되어 있어야 한다. 각각은 함수의 첫번째 인자 타입과 두 번째 인자 타입, 리턴 타입이다.

어댑터들은 인자로 받는 함수자의 위 정의 타입을 이용하여 컴파일 시간에 타입들을 검사합니다.

이러한 타입 정의를 쉽게하기 위해 STL은 기본 클래스 unary_function과 binary_function을 제공한다. 그래서 위 타입들을 직접 작성할 필요는 없습니다. 어댑터는 무조건 위 기본 클래스를 상속받아 만들면 됩니다. ㅡㅡ;

 

또 함수류(함수, 함수 포인터, 함수자) 중에서 bool형을 리턴하는(비교에 사용되는) 함수자를 predicate(조건자, 술어 구문)라 합니다.

 

2.1, 일반 함수자

일반 함수자에는 산술 연산, 비교 연산, 논리 연산 등을 수행하는 함수자들이 있습니다.

 

2.1.1, 산술 연산 함수자

 산술 연산자에는 plus, minus, multiplies, divides, modulus, negate가 있습니다. 모두 템플릿입니다.

 

plus의 간단한 예제입니다.

#include <iostream>
#include <functional>
using namespace std;

void main( )
{
    int result;

    plus<int> oPlus;
    result = oPlus( 10, 20); //oPlus 객체 생성 후 호출
    cout << result << endl;

    result = plus<int>()(10, 20); //임시 객체 생성 후 호출
    cout << result << endl;

}
  1. 30
    30

두 인자를 받아 합을 리턴하는 함수자입니다.

 

그렇다면 이런 함수자를 어디에 써먹나? ㅡㅡ;;

STL은 함수자없이는 동작할 수 없을 정도로 많은 곳에 함수자가 사용됩니다. 

plus 함수자를 사용하는 실제 예제를 보도록 하겠습니다.

 

plus 함수자를 사용한 trasform 알고리즘입니다.

 #include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

void main( )
{
    vector<int> v1;
    vector<int> v2;


    v1.push_back(10); 

    v1.push_back(20);
    v1.push_back(30);

    v2.push_back(1);
    v2.push_back(2);
    v2.push_back(3);

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;

    for(int i = 0 ; i < v2.size() ; i++)
        cout << v2[i] << " ";
    cout << endl;

    transform(v1.begin(), v1.end(), v2.begin(), v1.begin(), plus<int>());

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;
}

  1. 10 20 30
    1 2 3
    11 22 33

 v1.begin()~v1.end()까지의 원소와 v2의 원소를 함수자(plus<int>())로 연산하여 리턴 결과를 다시 v1.begin()부터 순서대로 저장하는 예제입니다.

그림으로 보면 아래와 같습니다.

plus.png 

 

 

STL plus를 사용하지 않고 사용자 Plus 함수자를 만들어 보도록 하겠습니다.

직접 작성한 Plus 함수자 예제입니다.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;


struct Plus
{
    int operator( )(int a, int b) const
    {
        return a+b;
    }
};
void main( )
{
    vector<int> v1;
    vector<int> v2;


    v1.push_back(10);

    v1.push_back(20);
    v1.push_back(30);

    v2.push_back(1);
    v2.push_back(2);
    v2.push_back(3);

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;

    for(int i = 0 ; i < v2.size() ; i++)
        cout << v2[i] << " ";
    cout << endl;

    transform(v1.begin(), v1.end(), v2.begin(), v1.begin(), Plus() );

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;
}

  1. 10 20 30
    1 2 3
    11 22 33

 결과는 같습니다. 직접 작성한 Plus 함수자를 사용했습니다.

 

STL의 plus처럼 어떤 타입의 합도 계산할 수 있도록 일반화 시키려면 템플릿 클래스로 만들면 됩니다.

다음은 Plus를 템플릿 클래스로 바꾼 예제입니다.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

template<typename T>
struct Plus
{
    T operator( )(const T& a, const T& b) const
    {
        return a+b;
    }
};
void main( )
{
    vector<int> v1;
    vector<int> v2;


    v1.push_back(10);

    v1.push_back(20);
    v1.push_back(30);

    v2.push_back(1);
    v2.push_back(2);
    v2.push_back(3);

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;

    for(int i = 0 ; i < v2.size() ; i++)
        cout << v2[i] << " ";
    cout << endl;

    transform(v1.begin(), v1.end(), v2.begin(), v1.begin(), Plus<int>() );

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;
}

  1. 10 20 30
    1 2 3
    11 22 33

단순히 템플릿 클래스로 변환한 것 외에 다른 것은 없습니다.

 

마지막으로 2, STL 함수자에서 언급했던 것처럼 만약 위에서 작성한 Plus<> 함수자 클래스를 어댑터 적용이 가능하도록 하려면 이항 함수자이므로 first_argument_type, second_argument_type, result_type을 정의해야 합니다. 가장 간단하게 정의할 수 있는 방법은 binary_function 기본 클래스를 상속받는 것입니다. 위 정의 타입이 필요한 이유는 어댑터에서 컴파일 시간에 타입 검사를 할때 위 정의 타입이 필요하기 때문입니다.

어댑터에 자세한 내용은 아래쪽에서 공부합니다.

 

다음은 binary_function을 상속받은 Plus<> 함수자 클래스입니다.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

template<typename T>
struct Plus : public binary_function<T, T, T>
{
    T operator( )(const T& a, const T& b) const
    {
        return a+b;
    }
};
void main( )
{
    vector<int> v1;
    vector<int> v2;

    v1.push_back(10);
    v1.push_back(20);
    v1.push_back(30);

    v2.push_back(1);
    v2.push_back(2);
    v2.push_back(3);

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;

    for(int i = 0 ; i < v2.size() ; i++)
        cout << v2[i] << " ";
    cout << endl;

    transform(v1.begin(), v1.end(), v2.begin(), v1.begin(), Plus<int>() );

    for(int i = 0 ; i < v1.size() ; i++)
        cout << v1[i] << " ";
    cout << endl;
}
  1. 10 20 30
    1 2 3
    11 22 33

지금 예제에는 어댑터를 사용하지 않기 때문에 굳이 필요하지는 않지만 다음을 위해서 어댑터 적용이 가능하도록 미리 binary_function을 상속받았습니다.

 

간단한 예제를 보이도록 하겠습니다.(binder1st<> 어댑터 사용)

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

void main( )
{
    vector<int> v;

    v.push_back(10);
    v.push_back(20);
    v.push_back(30);

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;

    transform(v.begin(), v.end(), v.begin(), binder1st< plus<int> > (plus<int>(), 100) );

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;
}
  1. 10 20 30
    110 120 130

v.begin()~v.end()까지의 모든 원소를 네 번째 함수자의 인자로 전달하고 리턴한 결과를 다시 v.begin()부터 순서대로 저장합니다.

 binder1st<>는 이항 연산자를 단항 연산자로 바꾸는 바인더 어댑터입니다. 위 예제의 transform이 단항 연산자를 필요로 하기때문에 이항 연산이 가능한 plus 함수자를 오른쪽 항을 100으로 고정시키고 왼쪽항만 인자로 받을 수 있는 단항 연산자 함수자로 변환합니다.

어댑터는 아래쪽에서 다시 자세히 공부합니다. 지금은 위와 같은 어댑터에 사용하는 plus 함수자는 binary_function 기본 클래스를 상속받아야 한다는 것에 집중하시면 됩니다.

 

우리가 만든 Plus<>가 binary_function을 상속받지 않으면 무시무시한(?) 에러가 발생합니다.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

template<typename T>
struct Plus
{
    T operator( )(const T& a, const T& b) const
    {
        return a+b;
    }
};
void main( )
{
    vector<int> v;

    v.push_back(10);
    v.push_back(20);
    v.push_back(30);

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;

    transform(v.begin(), v.end(), v.begin(), binder1st< Plus<int> > (Plus<int>(), 100) );

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;
}
  1. error C2039: 'second_argument_type' : 'Plus<T>'의 멤버가 아닙니다.
    1>        with
    1>        [
    1>            T=int
    1>        ]
    1>        c:\...\main.cpp(28) : 컴파일 중인 클래스 템플릿 인스턴스화 'std::binder1st<_Fn2>'에 대한 참조를 확인하십시오.
    1>        with
    1>        [
    1>            _Fn2=Plus<int>
    1>        ]
  2. ....

무서버~!

 

binary_function 기본 클래스를 상속받으면 쉽게 해결됩니다.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

template<typename T>
struct Plus : public binary_function<T, T, T>
{
    T operator( )(const T& a, const T& b) const
    {
        return a+b;
    }
};
void main( )
{
    vector<int> v;

    v.push_back(10);
    v.push_back(20);
    v.push_back(30);

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;

    transform(v.begin(), v.end(), v.begin(), binder1st< Plus<int> > (Plus<int>(), 100) );

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;
}
  1. 10 20 30
    110 120 130

 간단합니다.

 

우리가 직접 first_argument_type, second_argument_type, result_type을 정의해도 해결됩니다.

#include <iostream>
#include <functional>
#include <algorithm>
#include <vector>
using namespace std;

template<typename T>
struct Plus
{
    typedef T first_argument_type;
    typedef T second_argument_type;

    typedef T result_type;

    T operator( )(const T& a, const T& b) const
    {
        return a+b;
    }
};
void main( )
{
    vector<int> v;

    v.push_back(10);
    v.push_back(20);
    v.push_back(30);

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;

    transform(v.begin(), v.end(), v.begin(), binder1st< Plus<int> > (Plus<int>(), 100) );

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;
}

  1. 10 20 30
    110 120 130

상속이라는 더 깔끔한 방법이 있으니 binary_function을 상속받는 것이 더 좋겠죠?

 

minus, multiplies, divides, modulus, negate함수자는 plus와 모두 비슷하므로 생략합니다.

 

2.1.2, 비교 연산 함수자

 비교 연산자는 equal_to, not_equal_to, greater, less, greater_equal, less_equal이 있습니다. 모두 템플릿 클래스입니다.

 비교 연산 함수자는 모두 predicate입니다. predicate(조건자)는 비교를 수행하는(bool형을 리턴하는 ) 함수류(함수, 함수 포인터, 함수자)을 말합니다.

 

greater의 간단한 예제입니다.

#include <iostream>
#include <functional>
using namespace std;

void main( )
{
    int result;

    greater<int> oGreater;
    result = oGreater( 10, 20); //객체 생성 후 호출
    cout << result << endl;

    result = greater<int>()(10, 20); //임시 객체 생성 후 호출
    cout << result << endl;

    if( greater<int>()(50, 20) )
        cout << "true" << endl;
}
  1. 0
    0
    true

 greater는 두 인자를 받아 첫 번째 인자가 크면 true를 아니면 false를 리턴합니다.

 

 실제 사용 예제입니다.(sort 알고리즘)

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

void main( )
{
    vector<int> v;

    v.push_back(50);
    v.push_back(20);
    v.push_back(40);
    v.push_back(30);
    v.push_back(10);

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;


    sort(v.begin(), v.end()); 
    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;

    sort(v.begin(), v.end(), greater<int>() );

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;
}

  1. 50 20 40 30 10
    10 20 30 40 50
    50 40 30 20 10

 sort는 less를 사용하여 오름차순 정렬을 수행하지만 세 번째 인자로 greater를 전달하면 내림차순 정렬을 합니다.

 

 다음은 직접 작성한 Greater<>입니다.( 어댑터 적용이 가능하도록 binary_function을 상속 받았습니다.)

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

template<typename T>
struct Greater : public binary_function<T, T, bool>
{
    bool operator()(const T& a, const T& b) const
    {
        return a > b;
    }
};
void main( )
{
    vector<int> v;

    v.push_back(50);
    v.push_back(20);
    v.push_back(40);
    v.push_back(30);
    v.push_back(10);

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;


    sort(v.begin(), v.end()); 
    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;

    sort(v.begin(), v.end(), Greater<int>() );

    for(int i = 0 ; i < v.size() ; i++)
        cout << v[i] << " ";
    cout << endl;
}

  1. 50 20 40 30 10
    10 20 30 40 50
    50 40 30 20 10

결과는 같습니다.

 

나머지 비교 연산 equal_to, not_equal_to, less, greater_equal, less_equal은 greater와 비슷하므로 생략합니다.

 

2.1.3, 논리 연산 함수자

 논리 연산은  logical_and, logical_or, logical_not가 있습니다.

 

logical_and의 간단한 예제입니다.(논리 AND 연산을 수행하는 함수자)

#include <iostream>
#include <functional>
using namespace std;

void main( )
{
    int result;

    logical_and<int> and;
    result = and( 10 == 10, 1 < 2); //객체 생성 후 호출
    cout << result << endl;

    result = logical_and<int>()(10 > 20, 'A' < 'B'); //임시 객체 생성 후 호출
    cout << result << endl;

    if( logical_and<int>()(1, 1) )
        cout << "true" << endl;
}
  1. 1
    0
    true

 두 인자를 입력받아 두 인자 모두 true이면 true를 리턴하고 아니면 false를 리턴합니다. AND 연산!

 

간단한 사용 예제입니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

void main( )
{
    vector<bool> v1;
    vector<bool> v2;
    vector<bool> v3(3);


    v1.push_back(true);

    v1.push_back(false);

    v1.push_back(true);


    v2.push_back(false);
    v2.push_back(false);
    v2.push_back(true);

    transform(v1.begin(), v1.end(), v2.begin(), v3.begin(), logical_and<bool>() );

    for(int i = 0 ; i < v3.size() ; i++)
    {
        if( v3[i] )
            cout << "true  ";
        else
            cout << "false  ";
    }
    cout << endl;
}

  1. false  false  true

 간단하죠?

 

너머지는 모두 비슷하므로 생략합니다.

 

 오늘은 여기까지입니다. ^^

...

 

반응형

+ Recent posts