C++의 std::bind 함수와 플레이스홀더란?
개발을 하다 보면 기존 함수의 동작을 상황에 맞게 조정해야 할 때가 종종 있습니다. 예를 들어 특정 인수 값을 미리 고정한다거나, 함수가 호출될 때 전달되는 인수의 개수와 순서를 변경하는 작업 등이 그 예입니다.
C++11에서 새롭게 도입된 std::bind 함수는 이러한 작업을 훨씬 간편하게 처리할 수 있도록 도와주는 기능입니다. std::bind를 사용하려면 <functional> 헤더 파일을 포함해야 합니다.
std::bind는 플레이스홀더(placeholder)와 함께 사용할 때 진가를 발휘합니다. 플레이스홀더를 통해 어느 위치의 인수를 수정할지, 몇 개의 인수를 다룰지 지정할 수 있으며, 이를 바탕으로 원하는 출력에 맞게 함수를 자유롭게 변형할 수 있습니다.
플레이스홀더의 개념
플레이스홀더는 바인딩된 함수가 호출될 때 실제 값이 채워질 자리를 표시하는 역할을 합니다. _1, _2, _3 등으로 표현되며, 각각 호출 시 전달되는 첫 번째, 두 번째, 세 번째 인수에 대응됩니다. 이러한 플레이스홀더들은 std::placeholders 네임스페이스 안에 정의되어 있으므로, 사용 전에 해당 네임스페이스를 열어두어야 합니다.
예제 코드
#include <functional>
#include <iostream>
struct Foo {
Foo(int num) : num_(num) {}
void print_add(int i) const { std::cout << num_+i << '\n'; }
int num_;
};
void print_num(int i) {
std::cout << i << '\n';
}
struct PrintNum {
void operator()(int i) const {
std::cout << i << '\n';
}
};
int main() {
std::function<void(int)> f_display = print_num;
f_display(-9);
std::function<void()> f_display_42 = []() { print_num(42); };
f_display_42();
std::function<void()> f_display_31337 = std::bind(print_num, 31337);
f_display_31337();
std::function<void(const Foo&, int)> f_add_display = &Foo::print_add;
const Foo foo(314159);
f_add_display(foo, 1);
std::function<int(Foo const&)> f_num = &Foo::num_;
std::cout << "num_: " << f_num(foo) << '\n';
using std::placeholders::_1;
std::function<void(int)> f_add_display2 = std::bind( &Foo::print_add, foo, _1 );
f_add_display2(2);
std::function<void(int)> f_add_display3 = std::bind( &Foo::print_add, &foo, _1 );
f_add_display3(3);
std::function<void(int)> f_display_obj = PrintNum();
f_display_obj(18);
}
실행 결과
-9 42 31337 314160 num_: 314159 314161 314162 18
코드 해설
- f_display: 일반 함수 print_num을 std::function 객체에 담아 호출하여 -9를 출력합니다.
- f_display_42: 람다 표현식을 활용해 인수 없이 42를 출력하는 호출 가능 객체를 만듭니다.
- f_display_31337: std::bind로 첫 번째 인수를 31337로 고정했기 때문에, 별도 인수 없이 호출해도 31337이 출력됩니다.
- f_add_display: 멤버 함수 포인터 &Foo::print_add를 std::function에 저장합니다. 객체 foo와 1을 전달하여 314160(= 314159 + 1)이 출력됩니다.
- f_num: 멤버 변수 num_에 접근하는 std::function으로, foo의 num_ 값인 314159를 반환합니다.
- f_add_display2: 플레이스홀더 _1을 사용해 Foo 객체를 값으로 복사하여 바인딩합니다. 호출 시 전달된 2가 print_add의 매개변수 i 자리에 채워져 314161이 출력됩니다.
- f_add_display3: 객체의 주소(&foo)를 바인딩한 버전으로, 객체를 복사하지 않고 참조를 통해 동작하며 3이 더해진 314162를 출력합니다.
- f_display_obj: operator()를 오버로드한 함수 객체(PrintNum)를 std::function에 저장하여, 18을 인수로 호출합니다.
마무리
std::bind와 플레이스홀더를 활용하면 일반 함수는 물론 멤버 함수나 함수 객체까지 유연하게 재조합할 수 있습니다. 한편, 현대 C++에서는 람다 표현식이 가독성과 성능 면에서 더 권장되는 경우가 많으므로, 코드의 목적과 상황에 맞게 적절한 도구를 선택하는 것이 좋습니다.