728x90

함수는 함수 포인터 변수로만 보관할 수 있고, 함수자(functor)는 객체 타입으로만 보관이 가능합니다.

std::function을 사용하면 모든 collable 들을 객체형태로 보관할 수 있습니다. 심지어 람다식도 보관할 수 있습니다.

여기서 collable은 소괄호를 이용하여 호출 가능한 모든형태를 말합니다.

 

물론 auto를 통해서도 collable 들을 보관할 수도 있습니다. auto와 std::function의 차이는 뒤에 설명하겠습니다. 

일단, 간단한 사용 예시를 봅시다.

 

간단한 사용 예시

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

int func(const string& a)
{
    cout << "func1 " << a << endl;
    return 0;
}

struct functor
{
    void operator() (char c)
    {
        cout << "functor : " << c << endl;
    }
};


int main()
{
    function<int(const string&)> f1 = func;
    function<void(char c)> f2 = functor();
    function<void(void)> f3 = []() { cout << "lamda func" << endl; };

    f1("hi");
    f2('k');
    f3();
}

 

 

C에서의 함수 포인터에서도 멤버변수를 보관할 떄 문법이 약간 달라졌던 것 처럼 std::function 을 이용할 때도 동일합니다. 당연히 멤버함수는 객체 의존적인 함수이기 떄문입니다. 멤버함수는 암무적으로 자신을 호출한 객체를 인자로 암묵적으로 받고 있기 때문에 std::functio에 타입에 멤버함수의 클래스타입을 첫번째 인자로 명시해야 합니다.

 

함수 포인터와 마찬가지로 멤버함수를 보관할 때는 명시적으로 멤버함수 앞에 &를 통해 함수 주소를 반환하고 있습니다. 이게 FM 이죠. 정적, 전역함수의 경우 컴파일러에 의해서 암묵적으로 함수이름이 함수주소를 반환해주고 있었던거니 까요.

 

멤버함수를 보관하는 std::function 객체

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


class Knight
{
public:
    void AttackByFist() const
    {
        cout << "AttackByFist" << endl;
    }

    void AttackBySword(int addDamage)
    {
        cout << "AttackBySword" << endl;
    }
};

int main()
{
    function<void(const Knight&)> attackByFist = &Knight::AttackByFist; // <void(Knight&)> 형태로도 const 멤버 함수를 보관됩니다. 이유는 모르겠습니다.
    function<void(Knight&, int)> attackBySword = &Knight::AttackBySword;

    Knight k;

    attackByFist(k);
    attackBySword(k, 100);
}



mem_fn

mem_fn 함수는 멤버함수 function 객체를 반환합니다.

언제 mem_fn 함수가 유용할 할까요?

 

먼저 문제가 되는 상황을 살펴 보겠습니다.

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


class Knight
{
public:
    Knight(string name) : _name(name) {};
public:
    void Print()
    {
        cout << "I am Knight :: " << _name << endl;
    }

private:
    string _name;
};

template<typename Func>
void func(Func f)
{
    Knight k("by func");
    f(k);
}


int main()
{
	func(&Knight::Print);   // 컴파일 에러
}

이 경우 '이름()' 형태로 호출되지 않기 때문에 func 함수 내부에서 'f(k)'는 동작하지 않습니다. 따라서 collable 형태로 사용 가능하도록 function 객체로 넘겨 주어야 합니다.

 

        function<void(Knight&)> f = &Knight::Print;
        func(f);

이처럼 funtion 객체로 받고 func 인자로 넘겨주면 collable 형태로 호출이 가능하기 떄문에 정상 작동합니다. 이 과정을 대신 할수 있는 방법에는 두가지가 있습니다. std::mem_fn을 사용하거나 람다식을 이용하는 방법입니다. 

 

mem_fn 사용

        func(mem_fn(&Knight::Print));

람다식 사용

        func([](Knight& k) {
            k.Print();
        });

 

개인적으로는 mem_fn 코드는 더 짧지만 functional 헤더를 추가 해야하고 람다는 함수객체를 선언과 동시에 collabe 형태로 전달도 가능합니다.

람다는 auto와 같이 사용될 때 막강한 편의성을 제공합니다.

std::function을 이용하여 collabe 타입을 명시적으로 지정하고 특정 타입의 collabe만 받는 함수객체를 관리하고 싶을 때 유용한것 같습니다.

 

std::bind

함수객체 생성과 동시에 디볼트 인자값을 지정할 수 있습니다. 사용빈도가 떨어지는 것 같아 생략.

 

728x90

'Programming Language > C++' 카테고리의 다른 글

[CustomAllocator] 1단계_new, delete overloading  (0) 2022.02.13
::/스코프 지정자  (0) 2022.02.11
Modern C++/lambda/람다  (0) 2022.02.09
{} 중괄호 초기화  (0) 2022.02.06
using - type alias declaration  (0) 2022.02.06

+ Recent posts