[C++] 함수 포인터를 확장한 std::function 사용법

std::function

C++ 표준 라이브러리에서 제공하는 function 객체는 callable이라고 불리는 모든 대상을 저장하고 실행하는 객체입니다.

여기서 callable은 ()를 붙여서 호출할 수 있는 대상을 말합니다.

 

예를 들어, 함수는 함수명 뒤에 ()이 붙어있고 호출할 수 있습니다.

그래서, callable이라고 할 수 있습니다.

int normal_func( int a, int b){		// callable
    return a + b;
}

 

함수 객체( function object )도 callable입니다.

struct funcObj{ // 함수 객체, callable
    
    int operator()( int a, int b){
        return a + b;
    }
};

int main(){

    funcObj sumObj;
    int res = sumObj( 10, 12 );     // 함수 객체 호출
}

sumObj에 ()를 붙여서 sumObj개체의 operator()를 호출합니다.

 

함수 객체에 관한 내용은 여기에 정리해 두었습니다.

 

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

함수 객체( Function Object )함수 객체( Function Object )는 operator()를 구현한 타입을 말합니다.C++ 표준 라이브러리에서는, 펑터( Functor )라고도 불리는, 이 타입을 주로 컨테이너나 알고리즘의 정렬 기준

codingembers.tistory.com

 

그리고, 람다 표현식( lamda expression )도 callable입니다.

auto lamda = []( int a, int b)->int{ return a + b; };
int res = lamda( 10, 12 );  // callable

 

람다 표현식에 관한 내용은 여기에 정리했습니다.

 

[C++] 람다 표현식( lamda expression )에 대한 설명

람다 표현식( lamda expression )이란줄여서 람다( lamda )라고도 하는 람다 표현식은 익명의 함수 객체를 정의하고 사용하기 위한 표기법입니다.이 표현식은 간결한 기능을 구현하는데 너무 많은 손이

codingembers.tistory.com

 

이러한 함수나, 함수 객체, 람다 표현식 등을 모두 한 가지 타입의 객체에 저장하기 위해서 작성된 클래스가 std::function입니다.

 

이 객체를 사용하면 얻을 수 있는 장점은 함수 포인터를 사용하며 얻을 수 있는 장점을 그대로 가져옵니다.

게다가, 함수 외의 다른 타입까지도 저장할 수 있으므로 함수 포인터를 확장한 기능이라고 할 수 있습니다.

 

function 객체를 사용하는 이유로 여러 가지를 말할 수도 있지만, 한 마디로 하면 다른 객체나 함수에게 function 객체를 전달하기 위해서입니다.

그리고, 전달받은 객체나 함수는 작업 중 또는 작업 후에 전달받은 function 객체를 호출해서 자신의 목적을 달성할 수 있습니다. 
이러한 방식은 객체나 함수를 모듈화 하고 재사용하기 수월하게 만듭니다.
또한, 전달하는 function객체가 저장하고 있는 기능(함수)을 바꿈으로써 실시간으로 동적인 변화를 가져올 수도 있습니다.

 

function 객체 선언

function 객체를 사용하려면 먼저 헤더 파일을 포함해야 합니다.

#include <functional>

 

그리고, 선언은 다음과 같이 할 수 있습니다.

std::function< return_type ( parameter... ) > function_name;

 

반환 타입과 매개 변수는 모든 callable이 갖고 있는 특성입니다.

template를 통해서 이 특성을 가진 function 객체를 정의하고 있습니다.

 

아래 예제는 함수를 function 객체에 저장하는 예제입니다.

void int_func( int val ){}

void cstring_func1( const char* pstr ){}     // const char* 매개 변수

void string_func2( const string& str ){}    // const stirng& 매개 변수

int main(){

    function<void(int)> f = int_func;	// 함수를 저장
    
    function<void( const char*)> fcstr = cstring_func1;

    function<void( const char*)> fstr = string_func2;
}

이 예제를 보면, cstring_func1과 string_func2를 같은 타입의 function 객체에 저장할 수 있음을 볼 수 있습니다.

이것이 가능한 이유는 function 객체는 암시적인 변환이 가능하면 다른 타입의 매개 변수를 가진 함수라고 하더라도 저장할 수 있기 때문입니다.

하지만, 함수 포인터는 반환 값의 타입과 매개 변수의 타입이 반드시 일치해야 합니다.

 

다음 예제는 함수 객체와 람다를 저장하는 예제입니다.

struct funcObj{ // 함수 객체
        
    int operator()( int a, int b){
        return a + b;
    }
};

int main(){

    function<int(int, int)> fObj1 = funcObj();   // 함수 객체를 저장

    auto lamda = []( int a, int b)->int{ return a + b; };
    function<int(int, int)> fObj2 = lamda;  // 람다를 저장
    
    int result = fObj2( 10, 12);    // result: 22. function 객체를 실행
}

 

function 객체에 클래스 멤버 함수 저장

클래스 멤버 함수를 function 객체에 저장하기 위해서는,

멤버 함수에 대한 함수 포인터를 선언하기 위해 필요했던 사항들과 같은 것들이 필요합니다.

 

이해를 돕기 위해, 멤버 함수에 대한 함수 포인터에 관한 글이 도움이 될 것 같습니다.

그 글은 여기에서 볼 수 있습니다.

 

[C++] 클래스의 멤버 함수 포인터 (member function pointer)

멤버 함수 포인터와 함수 포인터의 차이점클래스의 멤버 함수 포인터( 이하, 멤버 함수 포인터 )와 일반 함수 포인터는 몇 가지 차이점이 있습니다. 일반 함수 포인터에 관한 자세한 내용은 여

codingembers.tistory.com

 

멤버 함수 포인터를 선언하는 방식과의 비교를 한다면,

클래스 멤버 함수를 function 객체에 저장하는 방법을 쉽게 알 수 있습니다.

 

아래 예제는 CSomething 클래스의 add 함수를 함수 포인터에 지정하는 방법을 보여주고 있습니다.  

#include <iostream>
using namespace std;

class CSomething{
    int m_nValue;

public:
    CSomething(int val) : m_nValue(val){}

    int add(int a, int b){		// 멤버 함수
        return m_nValue + a + b;	// 클래스 멤버 m_nValue에 접근
    }
};

int main(){

    CSomething cObj(10);
    
    // 멤버 함수 포인터를 선언하는 방법
    int (CSomething::*pFunc)(int, int) = CSomething::add;

    cout << (cObj.*pFunc)(2, 3) << endl;	// 멤버 함수 포인터 사용법
}

 

첫 번째, 멤버 함수 포인터를 선언하려면, 우선 어떤 클래스의 어떤 멤버 함수를 가리키려고 하는지를 명시해야 합니다.

CSomething::add;

이것은, function 객체를 선언할 때도 마찬가집니다.

따라서, 다음과 같은 선언이 될 것입니다.

function<int(int,int)> f = CSomething::add;

 

두 번째, CSomething 클래스의 어떤 객체의 함수를 호출할 것인지를 명시해야 합니다.

클래스 멤버의 함수는 객체에 따라 동작이 다를 수 있습니다.

CSomething A(10);
CSomething B(20);

int a = A.add(1,2);	// 13
int b = B.add(1,2);	// 23

위의 CSomething의 객체 A와 B의 add 함수를 같은 인자를 사용하여 호출하여도, 그 결과는 다릅니다.

따라서, function 객체에게 구체적인 CSomething 객체를 알려줘야 합니다.

 

클래스 멤버 함수를 호출하면, 암시적으로, 호출되는 함수를 가진 객체가 함수에 this로 전달됩니다.

예를 들어, 위의 A 객체의 add를 호출하면 인수들이 다음과 같이 전달됩니다.

add( &A, 1, 2 );	// A의 객체의 포인터를 전달

 

그래서, 필요한 두 가지를 모두 합하면 다음과 같이 선언해야 합니다.

function<int( CSomething&, int, int ) > f = &CSomething::add;

위에서, CSomething의 참조 타입을 인수로 한 것은, 객체가 복사되는 것은 막기 위한 것입니다.

그리고, &CSomething::add 대신 CSomething::add를 사용할 수도 있습니다.

 

마지막으로, function 객체를 실행할 때, 실제 호출하고자 하는 객체를 인자로 넘겨줘야 합니다.

#include <iostream>
#include <functional>	// for std::function
using namespace std;

int main(){

    CSomething cObj(10);
    
    // 멤버 함수 포인터
    int (CSomething::*pFunc)(int, int) = CSomething::add;
    cout << (cObj.*pFunc)(2, 3) << endl;

    // function 객체 
    function<int(CSomething, int,int)> f = CSomething::add;
    cout << f( cObj, 2, 3) << endl; 
}

▼출력

15
15

 

 

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