C++ Boost Function 및 Bind
Table of Contents
함수 포인터
C++
에는 다양한 종류의 포인터들이 존재한다.
- 포인터는 당연히, 특정한 메모리의 주소를 가리킬 수 있는 변수를 의미
- 메모리의 주소를 가리킬 수 있으므로, 다른 변수, 다른 객체, 다른 배열 등을 모두 가리킬 수 있다.
- 이에 추가적으로, 함수도 메모리에 위치한 값이므로, 함수 또한 가리킬 수 있다.
- 함수 포인터는
함수가 저장된 메모리를 가리키는 포인터
를 의미한다.
- 일반적으로 변수 포인터들은 변수 타입 뒤에
*
을 붙이는 방식으로 포인터를 정의한다.
int* a;
, double* b;
, MyClass* c;
등의 형태로 선언할 수 있다.
- 함수도 비슷하지만, 조금 선언이 까다롭다.
- 잘 생각해본다면, 포인터가 가지고 있는 것은 주소뿐이다.
- 타입이 필요한 것은 해당 주소부터 얼마나 데이터를 읽어올 지에 따라 달라진다.
- 그 말인 즉슨, 실제 저장된 주소는 모두 동일하더라도, 읽어오는 형태가 달라질 수 있다는 것을 의미한다.
- 그렇다면… 함수를 구분해주는 것은 무엇일까??
- 쉽게 생각한다면
Function Overloading
을 생각할 수 있을 것 같다.
Function Overloading
을 할 때, 함수들을 구분지어주는 시그니처는 함수명
, 함수 입력 파라미터의 개수
, 함수 입력 파라미터의 타입
세 가지가 된다.
- 그래서 비슷한 의미로
함수 입력 파라미터의 개수 및 타입
을 사용하기는 하는데, 왜 출력 타입
까지 사용하는 지는… 나도 잘 모르겠다. (아시는 분 댓글 달아주세요)
- 아무튼 함수 포인터를 구분할 때는
입력 파라미터의 개수
, 입력 파라미터의 타입
, 출력 타입
의 3가지로 구분할 수 있다.
- 선언은
int (*function_ptr) (int, int);
와 같은 식으로 선언할 수 있다.
- 위의 선언의 출력 타입이
int
, 입력 파라미터가 int
2개를 사용하는 함수 포인터를 의미한다.
- 아래와 같이 코드를 작성한 후, 실행해보면 결과를 볼 수 있다.
#include <iostream>
double sum(double a, double b)
{
return a + b;
}
int main()
{
int (*function_ptr) (int); // 입력이 int 파라미터 1개, 출력이 int인 함수
double (*d_function_ptr) (double); // 입력이 double 파라미터 1개, 출력이 double인 함수
double (*dd_function_ptr) (double, double); // 입력이 double 파라미터 2개, 출력이 double인 함수
dd_function_ptr = sum; // 입력이 double 파라미터 2개, 출력이 double인 함수 sum을 저장
std::cout << sizeof(function_ptr) << std::endl; // 함수 포인터도 마찬가지로 8바이트
std::cout << dd_function_ptr(0.1, 0.3) << std::endl; // 이와 같은 형태로 호출 가능
return 0;
}
함수 포인터를 쓰는 이유
- 함수 포인터라는게 뭔지는 이해가 됐는데, 함수는 그냥 호출만 하면 되는 거 아닌가? 왜 굳이 포인터를 이용까지해야 하지? 라는 생각이 들 수 있습니다.
- 하지만, 조금만 더 일반적인 경우를 생각한다면 Callback함수와 같은 형태로, 다른 함수의 파라미터로 전달할 수 있게 사용할 수 있다.
- 저 같은 경우는
ROS
에 관련된 내용을 하면서 Callback함수를 많이 다루게 되었고, 이에 따라 함수 포인터로 전달해주는 경우를 보면서 이를 정리하게 되었는데, 실상은 좀 다릅니다.
- 사실 함수 포인터는… C언어에 있는 문법인데, 잘 사용하지 않는 것 같습니다.
- 아니 아까 많이 쓴다며? 그게 사실
Boost
가 많이 좋아지면서, 일반화된 함수 포인터를 다룰 수 있게 되었기 때문이죠.
Boost Function
- 함수 포인터 얘기를 했는데, 일반화된(?) 함수 포인터란 뭘까요?
- 이것은 사실
C++
로 넘어오면서 생기는 문제인데, C언어에는 클래스가 없습니다.
- 그렇기 때문에 일반 함수만 존재하며, 멤버 함수는 존재하지 않습니다.
- 그렇다면 기존에 사용하던 함수 포인터를 이용하면 멤버 함수도 가리킬 수 있을까요?
- 물론 있습니다. 다음과 같은 방법을 이용한다면 가능합니다.
#include <iostream>
class TestClassA
{
public:
TestClassA(int a, int b, int c)
{
this->a_ = a;
this->b_ = b;
this->c_ = c;
}
void printStatus()
{
std::cout << "a, b, c = " << this->a_ << ", " << this->b_ << ", " << this->c_ << std::endl;
}
private:
int a_;
int b_;
int c_;
};
class TestClassB
{
public:
TestClassB(int a, int b, int c)
{
this->a_ = a;
this->b_ = b;
this->c_ = c;
}
void printStatus()
{
std::cout << "a, b, c = " << this->a_ << ", " << this->b_ << ", " << this->c_ << std::endl;
}
private:
int a_;
int b_;
int c_;
};
int main()
{
TestClassA A(1, 2, 3);
TestClassB B(10, 20, 30);
void (TestClassA::*function_ptr) () = TestClassA::printStatus;
void (TestClassA::*function_ptr) () = TestClassB::printStatus;
A.printStatus();
B.printStatus();
return 0;
}
- 대충 보시면 아시겠지만, 클래스에 포함된 멤버 함수의 경우, 해당 클래스의 함수 포인터를 적용해야합니다.
- 그러므로, 49번 줄의
void (TestClassA::*function_ptr) () = TestClassB::printStatus();
는 컴파일 오류가 발생합니다.
TestClassA
의 함수 포인터로 TestClassB
의 함수를 가리켰기 때문이죠.
- 내부 구현이나, 동작도 동일하고, 입력, 출력 타입도 동일한 것 같은데 안되는 게 이상하긴 합니다만, 아무튼 안됩니다.
- 저는 사실 몰랐는데,
C++
도 Python
과 마찬가지로, 멤버 함수들의 경우는 자동으로 첫 번째 파라미터로 this
포인터가 전달이 된다고 합니다. Python
의 self
와 마찬가지입니다만, C++
은 암시적으로 전달되며, Python
은 명시적으로 전달해줘야하죠.
- 그래서 이러한 차이 때문에 입력되는 타입이 맞지 않아서 컴파일 오류가 발생한다고 보시면 될 것 같습니다.
- 여기까지가 기존 사용하던 함수 포인터입니다.
- 그렇다면
일반화된 함수 포인터
는 어떨까요?
일반화된 함수 포인터
는 boost::function
을 통해서 사용할 수 있습니다.
boost::function
은 입력 파라미터의 개수 및 타입, 출력 타입만 맞다면 모두 적용할 수 있죠.
- 즉, 다음과 같이 사용할 수 있다는 뜻입니다.
#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>
class TestClassA
{
public:
TestClassA(int a, int b, int c)
{
this->a_ = a;
this->b_ = b;
this->c_ = c;
}
void printStatus()
{
std::cout << "a, b, c = " << this->a_ << ", " << this->b_ << ", " << this->c_ << std::endl;
}
private:
int a_;
int b_;
int c_;
};
class TestClassB
{
public:
TestClassB(int a, int b, int c)
{
this->a_ = a;
this->b_ = b;
this->c_ = c;
}
void printStatus()
{
std::cout << "a, b, c = " << this->a_ << ", " << this->b_ << ", " << this->c_ << std::endl;
}
private:
int a_;
int b_;
int c_;
};
int main()
{
TestClassA A(1, 2, 3);
TestClassB B(10, 20, 30);
// void (TestClassA::*function_ptr) () = TestClassA::printStatus;
// void (TestClassA::*function_ptr) () = TestClassB::printStatus;
boost::function <void ()> function_ptr;
function_ptr = boost::bind(&TestClassA::printStatus, A);
function_ptr();
function_ptr = boost::bind(&TestClassB::printStatus, B);
function_ptr();
// A.printStatus();
// B.printStatus();
return 0;
}
- 위와 같이 코드를 작성하면 오류 없이, 동일한 함수 포인터 1개로 두 종류의 클래스에 있는 멤버 함수를 모두 가리켜서 사용할 수 있습니다.
- 근데 그 중간에 보면 잘 모르는
boost::bind()
라는 놈이 있습니다. 이건 뭘까요?
Boost Bind
Boost Bind
는 쉽게 설명하면 함수를 우리가 원하는 형태로 변경할 수 있는 기능을 제공한다고 보면 됩니다.
- 이전에 말씀드린대로, 멤버 함수는 기본적으로
this
포인터를 암시적으로 입력받고 있습니다.
- 그렇다면 사실
TestClassB::printStatus()
라는 함수는 입력 파라미터가 1개인 셈이죠, TestClassB
타입의 파라미터 1개를 받고 있습니다.
- 그렇지만 실제 사용 시에는 암시적으로 전달되는
this
없이 사용하므로, 1개를 미리 bind
해줘야하는 것입니다.
- 이에 따라
boost::bind
를 통해, 특정 멤버 함수를 먼저 bind
하고, 그 첫 번째 파라미터인 this
에 해당하는 인스턴스를 넘겨주면 되는 것이죠.
- 이 외의 사용 방법도 많이 있는데, 예를 들어 3개의 입력이 있는 함수를 1개만 입력해서 호출하고 싶다면 그럴 때도
boost::bind
를 사용할 수 있습니다.
#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>
void printData(int a, int b, int c)
{
std::cout << "a = " << a << ", b = " << b << ", c = " << c << std::endl;
}
int main()
{
boost::function<void (int)> custom_print = boost::bind(&printData, 2, 3, boost::placeholders::_1);
custom_print(10);
custom_print = boost::bind(&printData, boost::placeholders::_1, 2, 3);
custom_print(10);
return 0;
}
- 위의 코드를 보면 입력이 3개의
int
를 받으며, 출력값은 없는 함수가 있다.
- 이를 상황에 따라 다르게 사용하기 위해
boost::bind
를 사용할 수 있다.
boost::bind
를 통해 함수를 bind
하고, 그 다음 입력으로 들어가는 값을 미리 지정할 수 있다.
- 위의 방법은 입력으로 들어가는 a에 2를, b에 3을, 그리고 c에는 입력으로 들어오는 첫 번째 파라미터를 넘겨주겠다는 의미이다. (
boost::placeholders::_1
은 자리 지시자로, 첫 번째 파라미터를 의미한다.)
- 아래의 방법은 입력으로 들어가는 a에 첫 번째 파라미터를, b에 2, c에 3을
bind
하는 것을 의미한다.
- 결과를 살펴보면
a = 2, b = 3, c = 10
이 먼저 출력되고 a = 10, b = 2, c = 3
이 나중에 출력된다.
- 따라서,
boost::bind
와 boost::placeholders::_1
등을 적절히 활용하면, 굳이 여러 개의 함수를 생성하지 않고 동일한 함수로 여러 가지 동작을 할 수 있다는 의미가 된다.
- 이에 추가적인 장점으로 호환되는 시그니처를 사용하는 경우, 담을 수 있다는 장점도 있다. (int 대신 short 타입을 사용한다거나 하는 경우, 기존의 함수 포인터는 불가능)
Review
C++
을 쓰기 전부터 Boost
가 유용하다, Boost
를 써본 사람보다 안써본 사람을 찾는게 더 어렵다는 얘기 등등… 많이 들었지만, 유용하긴 아주아주 유용하다.
- 그리고 현재는
Boost
에만 있는 기능이 아니라, STL
로 많이 넘어왔기 때문에, C++11
이상에서는 #include functional
을 선언한 후, std::function
, std::bind
, std::placeholders::_1
을 사용할 수도 있다.
- 아무튼
ROS
관련에서 Callback함수 사용 시에 아주 많이 사용하는 Boost::bind
를 알아보았다.