[C++] std::Function
C++에서 호출 가능한 모든 것을 포괄해서 나타내는 단어로 Callable이라는 단어를 사용한다. 대표적으로는 함수를 예로 들 수 있다. 하지만 C++에서는 ()를 붙여서 호출할 수 있는 모든 것을 Callable이라고 정의한다. 이해를 돕기 위해 아래의 코드를 확인해보자
#include <iostream>
struct A
{
void operator()(int a, int b)
{
std::cout << "a + b = " << a + b << std::endl;
}
}
int main()
{
A a;
a(5,6);
return 0;
}
위 코드를 실행할 경우 정상적으로 출력이 이뤄지면 출력 창에는 a + b = 8이라는 결과가 나온다. a를 ()를 통해 호출했다. a는 함수인가? 아니다. 그저 구조체 A의 객체일 뿐이다. 하지만 ()를 통해 함수처럼 호출하였다.
이처럼 ()를 통해 호출이 가능한 모든 Callable을 다룰 수 있는 클래스가 있다. 바로 std::function이다.
#include <functional>
#include <iostream>
#include <string>
int some_func1(const std::string& a)
{
std::cout << "Func1 호출! " << a << std::endl;
return 0;
}
struct A
{
void operator()(char c) { std::cout << "Func2 호출! " << c << std::endl; }
};
int main()
{
std::function<int(const std::string&)> f1 = some_func1;
std::function<void(char)> f2 = A();
std::function<void()> f3 = []() { std::cout << "Func3 호출! " << std::endl; };
f1("hello");
f2('c');
f3();
}
결과를 확인하면 셋 모두 정상적으로 호출됨을 알 수 있다. 여기서 알 수 있는 부분은 function 객체는 템플릿 인자로 전달 받을 함수의 타입을 갖게 된다. 반환형에 따라 다른 값을 넣어주면 된다.
더군다나 일반적인 함수 뿐만 아니라 위에서 다뤘던 구조체의 경우에도 연산자 오버로딩을 정의했을 뿐이데, 매개변수로 char를 받고 반환형이 void이기 때문에 정상적으로 작동이 되는 것을 알 수 있다.
세 번째의 경우 람다 함수인데, 아직 람다 함수의 경우 깊은 공부를 통하지 않았기 때문에 정확하게는 알 수 없으나 람다 함수의 경우에도 문제 없이 function에 담길 수 있었다.
하지만 예상 외의 상황이 발생하게 되는데, 바로 객체의 멤버 함수의 경우이다. 멤버 함수의 내부에서 this는 자신을 호출한 객체를 의미한다. 때문에 함수 내부에서 객체가 가진 private한 내용들까지 아무 제약 없이 접근이 가능한 것인데, 멤버 함수를 그냥 function에 넣게 되면 this, 즉 어느 객체에서 함수를 호출한 것인지 알 수 없게 된다.
#include <functional>
#include <iostream>
#include <string>
class A
{
int c;
public:
A(int c) : c(c) {}
int some_func() { std::cout << "내부 데이터 : " << c << std::endl; }
};
int main()
{
A a(5);
std::function<int()> f1 = a.some_func;
}
위 코드를 컴파일하게 되면 오류가 발생한다. f1을 실행하게 되면 자기 자신을 호출한 this를 찾아가게 되는데, 문제는 어느 객체의 some_func를 호출한 것인지 알 수가 없다. 때문에 저 형식을 취하려면 호출한 객체의 주소를 넣어주는 별도의 작업이 필요하다.
이 작업은 어떻게 이뤄질까? 크게 고민할 필요는 없다. 멤버함수는 명시되어 있지는 않지만 자신을 호출한 객체의 주소를 암묵적으로 매개변수, 즉 인자로 받고 있다. 예를 들면 위의 코드 내에서는 int some_func(this)인데 편의상 this가 생략된 것이다.
때문에 다음과 같이 코드를 변경해주면 된다.
#include <functional>
#include <iostream>
#include <string>
class A {
int c;
public:
A(int c) : c(c) {}
int some_func()
{
std::cout << "비상수 함수: " << ++c << std::endl;
return c;
}
int some_const_function() const
{
std::cout << "상수 함수: " << c << std::endl;
return c;
}
static void st() {}
};
int main()
{
A a(5);
std::function<int(A&)> f1 = &A::some_func;
std::function<int(const A&)> f2 = &A::some_const_function;
f1(a);
f2(a);
}
특이한 점은, 다른 함수들처럼 단순 이름으로 주소를 넘겨줄 수 없다는 점이다. 원래 함수의 이름은 주소값으로 암시적 변환인 일어나기 때문에 이름을 그냥 넣어서 사용이 가능하지만, 멤버 함수의 경우 암시적 변환이 발생하지 않기 때문에 반드시 &를 통해 주소값을 전달해야 정상적으로 작동을 한다.