본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : std::function (11)

by snowoods 2025. 9. 13.

Modern C++

std::function - 호출 가능한 모든 것을 위한 래퍼

 

개요

std::function은 C++11부터 도입된 표준 라이브러리의 기능으로, 함수 포인터, 함수 객체(Functor), 람다 표현식 등 호출 가능한(callable) 모든 것을 저장하고, 감싸고, 호출할 수 있는 다형성 래퍼(polymorphic wrapper)입니다. 이를 통해 다양한 형태의 호출 가능한 객체를 동일한 인터페이스로 처리할 수 있어 코드의 유연성과 재사용성을 크게 향상시킵니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++11 : std::function이 처음 도입되었습니다.

 

내용 설명

std::function은 템플릿 클래스로, std::function<ReturnType(ArgTypes...)>와 같은 형태로 선언합니다.

  • ReturnType: 함수의 반환 타입
  • ArgTypes...: 함수의 인자 타입 목록

주요 특징

  1. 타입 소거 (Type Erasure)
    • std::function은 서로 다른 타입의 호출 가능한 객체(예: 일반 함수, 람다, 함수 객체)를 동일한 std::function 타입의 객체에 저장할 수 있습니다.
    • 컴파일 타임에 구체적인 타입을 숨기고, 런타임에 동일한 방식으로 호출할 수 있게 해줍니다.
  2. 다형성 (Polymorphism)
    • 다양한 호출 가능 객체를 std::function 객체에 할당하고, 이를 통해 동일한 인터페이스로 호출할 수 있습니다.
    • 이러한 특징은 콜백(callback) 함수나 이벤트 핸들러, 전략(Strategy) 패턴 등을 구현할 때 매우 유용합니다.
  3. 상태 저장 (State Storage)
    • 람다 표현식과 함께 사용될 때, 캡처된 변수([&] 또는 [=] 등으로 캡처)를 포함한 람다의 상태를 안전하게 저장할 수 있습니다.

주의사항

std::function은 내부적으로 동적 할당을 사용할 수 있으므로(Small Buffer Optimization으로 작은 크기의 객체는 스택에 저장하기도 함), 일반 함수 포인터나 람다를 직접 사용하는 것보다 약간의 성능 오버헤드가 발생할 수 있습니다. 따라서 성능이 매우 중요한(performance-critical) 코드에서는 사용에 신중을 기해야 합니다.

 

예제 코드

#include <functional>
#include <iostream>
#include <vector>

// 일반 함수
int f(int arg)
{
    std::cout << "f(" << arg << ") called\n";
    return ++arg;
}

// std::function을 인자로 받는 함수
int new_approach2(std::function<int(int)> func)
{
    return func(2);
}

int main()
{
    auto param = int{1};

    // 1. C 스타일 함수 포인터
    std::cout << "--- C-style function pointer ---\n";
    int (*old_approach)(int);
    old_approach = f;
    old_approach(2);

    // 2. std::function에 일반 함수 저장
    std::cout << "\n--- std::function with a regular function ---\n";
    auto new_approach = std::function{f};
    auto result = new_approach(param);
    std::cout << "Result from new_approach(param): " << result << '\n';

    // 3. 함수에 std::function 전달
    std::cout << "\n--- Passing a function to another function ---\n";
    new_approach2(f);

    // 4. std::function에 람다 표현식 저장
    std::cout << "\n--- std::function with lambdas ---\n";
    const int threshold = 2;
    auto fns = std::vector<std::function<bool(const int)>>{
        [&threshold](const int v) { return v > threshold; },
        [&threshold](const int v) { return v < threshold; },
        [&threshold](const int v) { return v == threshold; },
        [&threshold](const int v) { return v != threshold; },
        [&threshold](const int v) { return v >= threshold; },
        [&threshold](const int v) { return v <= threshold; },
    };

    for (const auto &fn : fns)
    {
        std::cout << std::boolalpha << "Is 1 comparable? " << fn(1) << '\n';
    }

    return 0;
}

 

실행 결과

--- C-style function pointer ---
f(2) called

--- std::function with a regular function ---
f(1) called
Result from new_approach(param): 2

--- Passing a function to another function ---
f(2) called

--- std::function with lambdas ---
Is 1 comparable? false
Is 1 comparable? true
Is 1 comparable? false
Is 1 comparable? true
Is 1 comparable? false
Is 1 comparable? true

 

활용팁

  • 콜백 시스템: GUI 라이브러리의 버튼 클릭 이벤트나 네트워크 라이브러리의 데이터 수신 이벤트 등 비동기적인 작업의 콜백 함수를 등록할 때 std::function을 사용하면 다양한 형태의 콜백을 일관되게 처리할 수 있습니다.
  • 전략 패턴: 알고리즘의 특정 부분을 교체 가능하게 만들고 싶을 때, 알고리즘의 각 단계를 std::function으로 정의하여 런타임에 동적으로 변경할 수 있습니다.
  • 상태를 갖는 함수 객체: 람다의 캡처 기능을 활용하여 상태를 저장하는 함수 객체를 만들고, 이를 std::vector<std::function<...>>와 같은 컨테이너에 저장하여 관리할 수 있습니다. 예제 코드의 fns 벡터가 좋은 예시입니다.