[C++] std::function과 std::bind

조재훈·2024년 2월 15일

개요

Effective Modern C++ 책에서 람다 파트를 읽던 중 std::functionstd::bind가 언급되어서 공부하고 가면 좋을 것 같아 포스트를 써보려고 한다.

Callable

Callable이란, 이름 그대로 호출이 가능한 것을 의미한다. 대표적으로 함수가 있다.
C++에서는 ()을 붙여 호출할 수 있는 모든 것을 Callable이라고 정의한다.

#include "bits/stdc++.h"

using namespace std;

class Game
{
public:
	void operator()(int a, int b) { cout << "a = " << a << endl; cout << "b = " << b << endl; }
};

int main()
{
	Game game;
	game(10, 20);

	return 0;
}

다음을 컴파일하고 실행해보면 a = 10, b = 20이 출력된다.

하지만 game은 함수처럼 호출할 수 있지만 함수는 아니고 클래스 Game의 한 객체이다.

auto f = [](int a, int b) { cout << "a = " << a << endl; cout << "b = " << b << endl; };
f(10, 20);

f 역시 일반적인 함수의 꼴을 하고 있지는 않지만, ()를 통해 호출할 수 있기에 Callable이다.

std::function

C++에서 이러한 Callable 들을 객체의 형태로 보관할 수 있는 std::function 이라는 클래스를 제공한다.

  • <functional> 헤더에 정의되어 있으며 다형성 함수 래퍼를 나타내는 템플릿 클래스
  • 호출 가능한 개체를 저장, 복사 및 호출할 수 있다

C++11 표준에 추가되었으며 함수 포인터에 대한 보다 유연하고 안전한 대안을 제공하려는 목적으로 도입되었다.

C++11 이전에는 개발자가 유사한 기능을 달성하기 위해 함수 포인터나 사용자 정의 함수 개체를 사용했지만 C++11 이후 std::function로 동일한 기능을 할 수 있다.

예제

#include "bits/stdc++.h"

using namespace std;

class Game
{
public:
	void operator()(int a, int b) { cout << "a = " << a << endl; cout << "b = " << b << endl; }
};

void Add(int a, int b) { cout << "a + b = " << a + b << '\n'; }

int main()
{
	Game game;
	game(10, 20);

	auto f = [](int a, int b) { cout << "a = " << a << endl; cout << "b = " << b << endl; };
	f(10, 20);

	function<void (int, int)> f1 = Add;
	function<void(int, int)> f2 = game; // game을 호출 가능한 객체(펑터)로 처리하고 f2가 초기화됨
	function<void()> f3 = []() { cout << "std::function<void()>" << endl; };

	f1(10, 20);
	f2(20, 40);
	f3();

	return 0;
}

function 객체는 템플릿 인자로 전달 받을 함수의 타입을 가지게 됨

std::function은 C++의 모든 Callable을 마음대로 보관할 수 있는 유용한 객체이다. 만약 함수 포인터로 이를 구현하려고 했다면 Functor와 같은 경우를 성공적으로 보관하기 힘들었음

  • Functor : 함수 객체

멤버 함수를 가지는 std::function

function은 일반적인 Callable들을 쉽게 보관할 수 있었지만, 멤버 함수는 이야기가 달라진다.

왜냐하면, 멤버 함수 내에서 this의 경우 자신을 호출한 객체를 의미하기 때문에, 만일 멤버 함수를 그냥 function에 넣게 된다면 this가 무엇인지 알 수 없는 문제가 생긴다

class Game
{
public:
	Game(int InLevel) : level(InLevel) {}
	void operator()(int a, int b) { cout << "a = " << a << endl; cout << "b = " << b << endl; }
	void Print() { cout << level << endl; }

private:
	int level;
};

int main()
{
	Game game(5);

	function<void()> f4 = game.Print; // 컴파일 에러
	return 0;

컴파일 하면 오류 C3867 'Game::Print': 비표준 구문입니다. '&'를 사용하여 멤버 포인터를 만드세요. 라는 에러가 난다.

이는 함수 입장에서 자신을 호출하는 객체가 무엇인지 알 길이 없기 때문에 level이 어떤 객체의 level인지 알 수 없기 때문에 f4에 game에 관한 정보도 추가해야 한다.

멤버 함수들은 구현 상 자신을 호출한 객체를 인자로 암묵적으로 받고 있기 때문에 다음과 같이 바꿔주면 된다.

function<void(Game&)> f4 = &Game::Print;

위와 같이 원래 인자에 추가적으로 객체를 받는 인자를 전달해야 한다. 멤버 함수가 const이면 상수 형태로 인자를 받아야 한다.

이전 함수들과 다르게 함수의 이름만으로는 function에 함수의 이름 만으로는 그 주소값을 전달할 수 없다.

C++ 언어 규칙 때문에, 멤버 함수가 아닌 모든 함수의 경우 함수의 이름이 함수의 주소값으로 암묵적 변환이 일어나지만, 멤버 함수들의 경우 암묵적 변환이 일어나지 않으므로 & 연산자를 통해 명시적으로 주소값을 전달해야 한다

따라서 아래와 같이 호출하고자 하는 객체를 인자로 전달하면 해당 객체의 멤버 함수를 호출한 것과 같은 효과를 낼 수 있다

f4(game);

std::bind

bind 역시 C++11에서 도입되었으며, <functional> 헤더의 일부이다.

인수를 함수나 Callable에 바인딩하여 Callable을 만드는데 사용된다. 주로 function과 함께 사용된다.

함수 객체를 생성할 때 인자를 특정한 것으로 지정할 수 있
다. bind 함수의 반환은 function이다.

bind 함수를 사용하면 나중에 실제 함수가 호출될 때 제공될 수 있는 인수에 대한 자리 표시자가 있는 함수 객체를 생성할 수 있다.

예제

void Add(int a, int b) { cout << "a + b = " << a + b << '\n'; }

void Minus(int a, int b) { cout << "a - b = " << a - b << '\n'; }

int main()
{
	function<void(int)> func_add = bind(Add, placeholders::_1, 10);
	auto func_minus = bind(Minus, 15, placeholders::_1);

	func_add(15); // 10 + 15
	func_minus(20);  // 15 - 20
}

func_minus의 15와 placeholders::_1의 순서를 바꾸면 결과 값이 달라진다.

  • placeholders_숫자 : 내가 넣고 싶은 매개변수의 순서이다. 29까지 정의가능

참고

모두의코드

profile
나태지옥

0개의 댓글