C, C++/C++ 언어 / / 2024. 7. 20.

[C++] 함수 객체( Function Object )와 operator()

함수 객체( Function Object )

함수 객체( Function Object )는 operator()를 구현한 타입을 말합니다.

C++ 표준 라이브러리에서는, 펑터( Functor )라고도 불리는, 이 타입을 주로 컨테이너나 알고리즘의 정렬 기준이 필요할 때 사용합니다.

 

다음 예제에서는 함수 객체를 사용하는 법을 볼 수 있습니다.

class FuncObj{	// 함수 객체

public:
    int operator()(int a, int b){	// 이 연산자를 반드시 구현해야 합니다.
        return a + b;
    }
};

int Func(int a, int b){	// 일반 함수
    return a + b;
}

int main(){

    FuncObj f;			// 함수 객체 변수 선언
    int a = 5;
    int b = 6;

    int sum = f(a, b);		// operator()를 호출
    
    int sum2 = Func(a, b);	// 일반 함수 호출
    
    return 0;
}

함수 객체의 변수명을 통해서 operator()를 호출할 수 있습니다.

보는 바와 같이, 함수 객체를 사용하는 방법과 일반 함수를 사용하는 법이 유사합니다.

이것이 함수 객체라는 이름이 붙은 이유이기도 합니다.

 

그렇지만, 일반 함수보다 이 함수 객체를 사용할 때의 장점이 있습니다.

첫 번째, 함수 객체는 상태 정보를 담을 수 있습니다.

두 번째, 함수 객체( Function Object )는 타입이므로, 템플릿의 매개 변수가 될 수 있습니다.

 

함수 객체의 상태 정보

다음 예제는 배열의 원소들의 값을 3씩 증가시키는 코드입니다.

#include <algorithm>
using namespace std;

int increment(int n){	// transform에 전달될 함수
    return n + 3;
}

int main(){

    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr)/sizeof(arr[0]);
    int res[n];
 
    std::transform(arr, arr+n, res, increment);
 
    for (int x : res){
        cout << x << " ";
    }

    return 0;
}

여기에 쓰인 transform 함수는 arr의 처음부터 끝까지의 원소를 대상으로 increment 함수를 호출하여, 그 결과를 res 변수에 저장하는 함수입니다.

그런데, increment 함수는 매개 변수를 하나밖에 사용하지 않습니다.

왜냐하면, transform이 매개 변수를 하나만 요구하는 함수를 필요로 하기 때문입니다.

 

그래서, 만약 배열의 원소에 3 대신 5를 더하고 싶다면, 다시 함수를 작성해야 합니다.

아니면, 전역 변수를 사용해서 추가적인 정보를 제공하는 방법도 있습니다.

그러나, 이런 식으로 코드를 휘저어 놓으면, 그렇게 즐겁지는 않을 겁니다.

 

그러나, 함수 객체는 필요한 정보를 다양한 방법으로 가질 수 있습니다.

class incrementObj{	// 함수 객체
    int m_increment;	// 상태 정보
public:

    incrementObj(int incre) : m_increment(incre){}

    int operator()(int n){
        return n + m_increment;
    }
};

int main(){

    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr)/sizeof(arr[0]);
    int res[n];
    int amount = 5;

    std::transform(arr, arr+n, res, incrementObj(amount));  // 함수 객체 사용
 
    for (int x : res){
        cout << x << " ";
    }

    return 0;
}

위와 같이, 함수 객체의 생성자를 통해서 필요한 정보를 제공하거나, 멤버 함수를 통해서 정보를 넣을 수도 있습니다.

그리고, 아예 값을 증가시키는 기능을 필요에 따라 곱하거나 나누는 연산으로 바꿀 수도 있습니다.

 

참고로, transform 함수는 매개 변수를 두 개 사용하는 함수를 사용하는 버전이 있습니다.

예를 들어, 두 배열의 값을 더한 결과를 다른 배열에 저장하는 기능이 필요할 때 이 버전을 사용합니다.

transform에 관한 내용은 여기에서 볼 수 있습니다.

 

[C++] transform 사용법

transform 기본 사용법transform은 지정된 함수 객체를 입력된 범위의 모든 원소들에게 적용하고, 그 결과를 대상 범위에 출력하는 함수입니다. 이 함수를 사용하려면 먼저 다음 헤더를 포함해야 합

codingembers.co.kr

 

템플릿( template )의 매개 변수로서의 함수 객체

C++ Standard Library는 다양한 함수 객체를 <functional> 헤더 파일을 통해서 제공하고 있습니다.

그리고, 이런 함수 객체들을 사용해서 표준 라이브러리 컨테이너의 목표를 달성하고 있습니다.

 

예를 들어, std::set의 선언은 다음과 같습니다.

template <class Key,
    class Traits=less<Key>,
    class Allocator=allocator<Key>>
class set

이 중, 두 번째 매개 변수가 set 컨테이너의 원소를 정렬하는 데 사용되는 함수 객체입니다.

 

less 함수 객체의 선언은 다음과 같습니다.

template <class Type = void>
struct less : public binary_function <Type, Type, bool>
{
    bool operator()(const Type& Left, const Type& Right) const;
};

이 함수 객체는 매개 변수 Left가 Right보다 작으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

이 반환 정보를 사용해서 std::set은 원소들을 정렬합니다.

 

물론, 다른 식으로 원소들을 정렬할 수도 있습니다.

예를 들어, 내림차순으로 정렬하고 싶다면, C++ 이 제공하는 greater 함수 객체를 사용할 수 있습니다.

또는, 사용자 함수 객체를 작성해, 필요에 따른 정렬 방식을 제공할 수도 있습니다.

template<typename T>    // 사용자 함수 객체
struct compObj{
    bool operator()( const T& a, const T& b){
        return a > b;	// 내림차순으로 정렬
    }
};

int main(){

    std::set< int, compObj<int> > s;
    s.insert(3);
    s.insert(5);
    s.insert(7);

    return 0;
}

 

함수 객체 대신 함수를 사용할 수도 있는데, 약간 더 번거롭습니다.

이 때는, 함수 서명을 두 번째 인수로 넘기고, 사용될 구체적인 함수를 set의 생성자를 통해서 알려줘야 합니다.

template<typename T>
bool comp( const T& a, const T& b){	// 일반 비교 함수
    return a > b;
}

template<typename T>
using CompType = bool(*)(const T&, const T&);	// 비교 함수 타입

int main(){

    set< int,  CompType<int> > s(comp<int>);
    s.insert(3);
    s.insert(5);
    s.insert(7);
}

 

C++ 11에 도입된 람다 함수를 사용하면 일반 함수를 사용하는 것보단 편리해집니다.

template<typename T>
auto lamda = [](const T&a, const T&b){	// 람다 함수
    return a > b;
};

int main(){

    set< int, decltype(lamda<int>) > s(lamda<int>);
    s.insert(3);
    s.insert(5);
    s.insert(7);
}

람다 함수의 타입은 컴파일러만 알 수 있는 고유한 타입이기 때문에, decltype 함수를 사용해서 람다의 타입을 알려주어야 합니다.

 

알고리즘에서 함수 객체 사용

std::sort 함수는 컨테이너나 배열의 원소를 원하는 방식으로 정렬하는 함수입니다.

 

이 함수는 정렬을 원하는 부분을 정하는 매개 변수와 두 원소를 비교하는 함수를 매개 변수로 받는 함수입니다.

이때도, 두 원소를 비교하는 함수 대신 같은 역할을 하는 함수 객체를 사용할 수 있습니다.

 

다음 예제에서, 내림차순으로 정렬하는 함수 객체 greater를 볼 수 있습니다.

이 객체도 <functional> 헤더 파일에서 볼 수 있습니다.

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

int main(){

    int arr[] = {1, 2, 3, 4, 5};
    int n = sizeof(arr)/sizeof(arr[0]);
    
    sort( arr, arr + n, greater<int>());    // 내림차순으로 정렬
 
    for( int x : arr){
        cout << x << " ";
    }

    return 0;
}

▼출력

5 4 3 2 1

이 때는, 함수 객체( 타입 )의 객체를 요구하기 때문에, greater <int>가 아니라, greater <int>()를 함수에 인수로 알려줘야 합니다.

 

std::sort 함수에 관한 좀 더 자세한 설명은 여기에서 볼 수 있습니다.

 

[C++] STL sort 함수 사용법

기본 사용법 sort 함수는 지정된 범위에 있는 요소를 기본적인 오름차순 또는 지정한 정렬 기준에 따라 정렬합니다. sort 함수를 사용하기 위해서는 우선 헤더파일을 포함해야 합니다. #include 함수

codingembers.co.kr

 

이 글과 관련된 글들

함수 포인터를 확장한 std::function 사용법

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유