std::function과 std::bind
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 86번째.
람다는 강력하지만, 때로는 서로 다른 타입의 호출 가능한 객체들을 통일된 방식으로 저장하거나, 기존 함수를 필요한 시그니처에 맞게 조정해야 할 때가 있습니다. <functional> 헤더의 std::function과 std::bind는 이러한 문제들을 해결해 줍니다.
std::function은 특정 시그니처와 일치하는 모든 호출 가능한 객체(함수, 람다 또는 함수 객체)를 보유할 수 있는 타입 소거(type-erased) 래퍼입니다:
#include <iostream>
#include <functional>
int add(int a, int b) { return a + b; }
int main() {
std::function<int(int, int)> operation;
operation = add; // 일반 함수
std::cout << operation(3, 4) << "\n"; // 7
operation = [](int a, int b) { return a * b; }; // 람다
std::cout << operation(3, 4) << "\n"; // 12
}std::bind는 기존 함수의 일부 인수를 고정하여 새로운 호출 가능한 객체(callable)를 생성합니다. std::placeholders::_1, _2 등을 사용하여 가변적으로 남겨둘 인수를 표시합니다:
#include <iostream>
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
int main() {
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1);
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1);
swapped("Bob", "Hi"); // Hi, Bob!
}이러한 도구들은 콜백을 클래스 멤버로 저장하거나 전략 패턴(Strategy pattern)을 구현할 때 OOP에서 특히 유용합니다. 하지만 현대적인 C++에서는 더 나은 가독성과 성능을 위해 std::bind보다 람다를 선호하는 경우가 많습니다. 주로 인자 순서를 변경해야 하거나 레거시 코드를 작업해야 할 때 std::bind를 사용하세요.
챌린지
쉬움std::function과 std::bind의 강력함을 보여주는 설정 가능한 계산기를 만들어 봅시다. 수학적 연산을 런타임에 저장, 교체 및 사용자 정의할 수 있는 시스템을 구축하여, 이러한 도구들이 어떻게 유연한 콜백 관리를 가능하게 하는지 배우게 됩니다.
코드는 다음 세 개의 파일로 구성됩니다:
MathOperations.h: 연산 라이브러리 역할을 할 독립적인 수학 함수 모음을 정의합니다.다음 함수들을 생성하세요:
add(int a, int b)— 합계를 반환합니다.subtract(int a, int b)— 차(a - b)를 반환합니다.multiply(int a, int b)— 곱을 반환합니다.power(int base, int exponent, int multiplier)—multiplier * (base ^ exponent)를 반환합니다. 거듭제곱을 계산하기 위해 간단한 루프를 사용하세요 (지수는 0 이상이라고 가정합니다).
Calculator.h:std::function을 사용하여 연산을 동적으로 저장하고 실행하는Calculator클래스를 정의합니다.Calculator는 다음을 포함해야 합니다:
- 현재의 이항 연산을 저장하기 위한 private 멤버
std::function<int(int, int)> setOperation(std::function<int(int, int)> op)— 현재 연산을 설정합니다.calculate(int a, int b)— 저장된 연산을 실행하고 결과를 반환합니다.
필요한 헤더(
<functional>)를 포함하고 MathOperations 헤더를 포함해야 합니다.- 현재의 이항 연산을 저장하기 위한 private 멤버
main.cpp: 세 개의 입력을 읽습니다:- 연산 이름:
add,subtract,multiply, 또는square - 첫 번째 숫자 (정수)
- 두 번째 숫자 (정수)
Calculator를 생성하고 연산 이름에 따라 설정합니다:add,subtract,multiply의 경우: 해당 함수를 계산기에 직접 할당합니다.square의 경우:std::bind를 사용하여 지수는 항상 2이고 배율(multiplier)은 1인power의 특수 버전을 만듭니다. 바인딩된 함수는 두 개의 인자를 받되, 첫 번째 인자만 밑(base)으로 사용해야 합니다 (두 번째 인자는 placeholder를 사용하여 무시할 수 있습니다).
연산을 설정한 후, 두 숫자로
calculate()를 호출하고 다음을 출력합니다:Result: [value]그런 다음
a + b + 100을 반환하는 람다를 계산기에 할당하여 연산 교체를 시연하고, 동일한 입력으로 실행한 후 다음을 출력합니다:With bonus: [value]- 연산 이름:
예를 들어, 입력이 add, 10, 5인 경우:
Result: 15
With bonus: 115입력이 multiply, 7, 3인 경우:
Result: 21
With bonus: 110입력이 square, 4, 0인 경우:
Result: 16
With bonus: 104이 챌린지는 std::function이 다양한 호출 가능 타입(일반 함수, 바인딩된 함수, 람다)을 저장하는 통일된 방법을 제공하는 방식과, std::bind가 일부 인자를 고정하여 기존 함수를 적응시키는 방법을 보여줍니다.
치트 시트
<functional> 헤더는 호출 가능한 객체들을 유연하게 다루기 위한 std::function과 std::bind를 제공합니다.
std::function은 특정 시그니처와 일치하는 모든 호출 가능한 객체를 저장할 수 있는 타입 소거(type-erased) 래퍼입니다:
#include <functional>
std::function<int(int, int)> operation;
operation = add; // 일반 함수
operation = [](int a, int b) { return a * b; }; // 람다std::bind는 기존 함수의 일부 인수를 고정하여 새로운 호출 가능한 객체를 생성합니다. 가변 인수에는 std::placeholders::_1, _2 등을 사용하세요:
#include <functional>
void greet(const std::string& greeting, const std::string& name) {
std::cout << greeting << ", " << name << "!\n";
}
using namespace std::placeholders;
auto sayHello = std::bind(greet, "Hello", _1); // 첫 번째 인수를 고정
sayHello("Alice"); // Hello, Alice!
auto swapped = std::bind(greet, _2, _1); // 인수 순서 변경
swapped("Bob", "Hi"); // Hi, Bob!이러한 도구들은 콜백을 클래스 멤버로 저장하거나 유연한 콜백 시스템을 구현할 때 유용합니다. 현대적인 C++에서는 가독성을 위해 std::bind보다 람다를 사용하는 것을 선호하는 경우가 많습니다.
직접 해보기
#include <iostream>
#include <string>
#include <functional>
#include "Calculator.h"
using namespace std;
int main() {
// 입력 읽기
string operation;
int num1, num2;
cin >> operation >> num1 >> num2;
// Calculator 인스턴스 생성
Calculator calc;
// TODO: 연산 이름에 따라 계산기 설정
// "add", "subtract", "multiply"의 경우: 해당 함수를 직접 할당
// "square"의 경우: std::bind를 사용하여 power의 특수화된 버전 생성
// 항상 지수 2와 배수 1을 사용하도록 설정
// 힌트: 밑(base) 인자로 std::placeholders::_1을 사용
if (operation == "add") {
// 여기에 코드 작성
} else if (operation == "subtract") {
// 여기에 코드 작성
} else if (operation == "multiply") {
// 여기에 코드 작성
} else if (operation == "square") {
// 여기에 코드 작성 - power 함수와 함께 std::bind 사용
}
// TODO: calculate()를 호출하고 결과 출력
// 형식: "Result: [value]"
// TODO: 연산 교체 시연
// a + b + 100을 반환하는 람다를 계산기에 할당
// calculate()를 다시 호출하고 결과 출력
// 형식: "With bonus: [value]"
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
12Modern C++ 주요 기능
이동 시맨틱과 Rvalue완벽한 전달람다 표현식 심화std::function과 std::bindconstexpr와 consteval구조화된 바인딩optional, variant, any