std::function은 단순 함수뿐만 아니라 operator(), 람다, 멤버 함수, std::bind 등을 포함한 모든 callable 객체를 저장하고 호출할 수 있는 다형적인 함수 래퍼 클래스입니다.
int some_function(const std::string& s)
{
std::cout << s << "함수 호출!" << std::endl;
return 0;
}
struct S
{
void operator()(const std::string& s)
{
std::cout << "구조체 오퍼레이터 함수 호출!" << std::endl;
}
};
int main()
{
std::function<int(const std::string& s)> f1 = some_function;
std::function<void(const std::string& s)> f2 = [](const std::string& s) -> void { std::cout << "람다 함수 호출" << s << std::endl;};
std::function<void(const std::string& s)> f3 = S();
f1("sss");
f2("sss");
f3("sss");
return 0;
}
템플릿 인자로 함수의 타입(반환, 인자)를 받아 함수 객체를 보관하는 역할
멤버함수의 경우엔?
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;
}
내부 함수의 c가 어떤 객체의 c를 참조하는지 알 수 없기에 오류가 발생한다.
즉, 멤버 함수는 객체 없이 단독 호출이 불가능하기 때문에, std::function에 넣을 때는 객체 참조를 인자로 받는 형식으로 감싸야 합니다.
그렇기에
class A {
int c;
public:
A(int c) : c(c) {}
int some_func() { std::cout << "내부데이터: " << c << std::endl; }
int some_const_function() const { std::cout << "내부데이터, const: " << c << std::endl; }
};
int main() {
A a(5);
// 비-const 멤버 함수 포인터
std::function<int(A&)> f1 = &A::some_func;
f1(a);
// const 멤버 함수 포인터
std::function<int(const A&)> f2 = &A::some_const_func;
f2(a);
}
함수 이름만으로는 멤버 함수 포인터로 암시적 변환되지 않기 때문에 &A::some_func처럼 명시적 주소 연산자가 필요합니다.
멤버 함수 포인터를 함수 객체로 변환해주는 도우미 함수입니다. 객체를 인자로 넘기기만 하면 됩니다.
#include <iostream>
#include <functional>
#include <vector>
class A {
public:
void print() const { std::cout << "mem_fn 예제" << std::endl; }
};
int main() {
A a;
auto f = std::mem_fn(&A::print);
f(a); // mem_fn을 이용한 멤버 함수 호출
std::vector<A> vec(3);
for (const auto& item : vec) f(item); // STL 컨테이너에도 적용 가능
}
std::bind는 기존 함수나 멤버 함수에 인자를 고정(bind) 시켜서 새로운 호출 가능한 객체를 만듭니다.
#include <iostream>
#include <functional>
void greet(const std::string& name, int times) {
for (int i = 0; i < times; ++i)
std::cout << "Hello, " << name << "!\n";
}
int main() {
auto say_hello = std::bind(greet, "Alice", 3);
say_hello(); // Hello, Alice! x3
auto say_n_times = std::bind(greet, std::placeholders::_1, 2);
say_n_times("Bob"); // Hello, Bob! x2
}
std::placeholders::_1 여러 숫자가 있는데 인자의 순서를 나타낸다.
auto g = std::bind(show, std::placeholders::_2, std::placeholders::_1, 300);
이런식으로 할 경우 g(2,3)이라고 할 경우 -> (3,2)의 순서로 나타난다.
레퍼런스의 경우 명시적으로 std::ref(s1) 전달해주지 않으면 데이터가 변경되지 않을 수 있는데 인자가 복사되어 전달되기 때문이다.
#include <iostream>
#include <functional>
void modify(std::string& s) {
s += " world";
}
int main() {
std::string s = "hello";
// 값 복사 - 원본 변경 안 됨
auto f1 = std::bind(modify, s);
f1();
std::cout << s << std::endl; // 출력: hello
// 참조로 전달
auto f2 = std::bind(modify, std::ref(s));
f2();
std::cout << s << std::endl; // 출력: hello world
}
std::bind는 인자를 기본적으로 값으로 복사하므로, 원본 객체를 수정하고 싶다면 반드시 std::ref()로 참조 전달해야 합니다.
멤버 함수 바인딩의 경우에도
class A {
public:
void hello(const std::string& msg) {
std::cout << "A says: " << msg << "\n";
}
};
A a;
auto f = std::bind(&A::hello, &a, "Hi");
f(); // A says: Hi
void A::hello(A* this, const std::string& msg);
내부적으론 이런형태이기 때문에 꼭 주소를 명시를 해주어야 한다.