Menu
Coddy logo textTech

std::function과 std::bind

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 86번째.

람다는 강력하지만, 때로는 서로 다른 타입의 호출 가능한 객체들을 통일된 방식으로 저장하거나, 기존 함수를 필요한 시그니처에 맞게 조정해야 할 때가 있습니다. <functional> 헤더의 std::functionstd::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를 사용하세요.

challenge icon

챌린지

쉬움

std::functionstd::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 헤더를 포함해야 합니다.

  • main.cpp: 세 개의 입력을 읽습니다:
    1. 연산 이름: add, subtract, multiply, 또는 square
    2. 첫 번째 숫자 (정수)
    3. 두 번째 숫자 (정수)

    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::functionstd::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;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨