삶의 공유

C++ 함수 객체(Function Object) 완전 정리 본문

Programing언어/C++

C++ 함수 객체(Function Object) 완전 정리

dkrehd 2025. 9. 29. 22:54
728x90
반응형

📘 C++ 함수 객체(Function Object) 완전 정리

C++ 프로그래밍을 하다 보면 함수를 값처럼 전달해야 하는 경우가 많습니다. 이때 단순 함수 포인터보다 더 강력하고 유연하게 쓸 수 있는 것이 바로 함수 객체(Function Object, Functor) 입니다.

이번 글에서는 함수 객체가 무엇인지, 왜 필요한지, 그리고 STL과 람다와의 관계까지 차근차근 정리해보겠습니다.


1. 함수 객체(Function Object)란?

operator() 연산자를 재정의하여 함수처럼 호출할 수 있는 객체

 
struct Print {
    void operator()(int x) const {
        std::println("x = {}", x);
    }
};

int main() {
    Print p;
    p(42);               // 함수처럼 호출
    // 실제 호출은 p.operator()(42);
}
 
  • p(42) → 사실은 p.operator()(42) 호출
  • 즉, 객체이지만 함수처럼 동작하는 특별한 클래스

2. 함수 객체를 사용하는 이유

그렇다면 왜 굳이 함수 대신 함수 객체를 쓸까요? 장점은 다음과 같습니다.

(1) 상태를 가질 수 있다

함수 객체는 클래스이므로 멤버 변수를 가질 수 있습니다.

 
struct Threshold {
    int th;
    explicit Threshold(int t): th(t) {}
    bool operator()(int x) const { return x >= th; }
};

Threshold check10(10);
std::println("{}", check10(7));   // false
std::println("{}", check10(15));  // true
 

(2) 클로저(Closure)

람다(lambda)는 사실상 익명 함수 객체입니다.
캡처한 변수들이 함수 객체의 상태가 됩니다.

 
int base = 5;
auto add_base = [base](int x) { return x + base; };
std::println("{}", add_base(10)); // 15
 

(3) 인라인 최적화

  • 함수 포인터는 인라인 최적화가 어렵지만,
  • 함수 객체는 템플릿 인자로 넘기면 인라인될 수 있어 성능에 유리합니다.
 
std::sort(v.begin(), v.end(), std::greater<>()); // greater는 함수 객체
 

(4) 이름 충돌 방지

전역 함수 이름 충돌을 피할 수 있습니다.
struct Less { bool operator()(int, int) const; }; 처럼 타입 이름으로 관리되기 때문에 안전합니다.


3. 표준 라이브러리 함수 객체

C++ 표준 <functional> 헤더에는 자주 쓰이는 함수 객체들이 이미 정의되어 있습니다.

  • 산술: std::plus<>, std::minus<>, std::multiplies<>
  • 비교: std::less<>, std::greater<>
  • 논리: std::logical_and<>, std::logical_or<>
  • 해시: std::hash<T>
 
 
#include <functional>
#include <print>

int main() {
    std::plus<int> add;
    std::println("{}", add(10, 3)); // 13
}

4. STL 알고리즘과 함수 객체

STL 알고리즘은 함수 객체와 함께 사용할 때 강력해집니다.

(1) 정렬 기준

 
struct Desc {
    bool operator()(int a, int b) const { return a > b; }
};

std::vector<int> v{3,1,4,2};
std::sort(v.begin(), v.end(), Desc{});
// 4,3,2,1
 

(2) 변환

 
struct Times {
    int k;
    explicit Times(int kk): k(kk) {}
    int operator()(int x) const { return x * k; }
};

std::vector<int> v{1,2,3}, out(v.size());
std::transform(v.begin(), v.end(), out.begin(), Times{10});
// out = 10, 20, 30
 

5. 함수 객체와 람다

람다는 사실 익명 함수 객체를 생성하는 문법 설탕입니다.

 
auto f = [](int x) { return x * 2; };
static_assert(std::is_class_v<decltype(f)>); // 람다도 클래스 타입
 

즉, 함수 객체의 개념을 이해하면 람다도 자연스럽게 이해할 수 있습니다.


6. std::function 과 함수 객체

  • std::function<R(Args...)> 은 모든 호출 가능한 객체(callable) 를 담을 수 있는 래퍼입니다.
  • 함수 포인터, 함수 객체, 람다 모두 저장 가능.
  • 다만 타입 소거(type erasure)로 인해 성능 오버헤드가 있을 수 있습니다.
 
#include <functional>
#include <print>

void hello(int n) { std::println("hello {}", n); }

int main() {
    std::function<void(int)> f1 = hello;          // 함수
    std::function<void(int)> f2 = [](int n){ std::println("lambda {}", n); }; // 람다
    f1(1);
    f2(2);
}
 

7. 정리

  • 함수 객체 = 객체지만 함수처럼 호출 가능
  • 함수 포인터보다 강력한 이유:
    1. 상태 유지 가능
    2. 람다와 클로저의 기반
    3. 인라인 최적화 가능
    4. 이름 충돌 방지
  • STL과 표준 함수 객체(std::plus, std::less, std::hash)에서 광범위하게 활용

👉 실무에서는 함수 객체와 람다를 적절히 활용하면, 코드가 더 유연하고 성능도 좋아집니다.


📌 한 줄 요약

함수 객체는 “객체 + 함수”의 장점을 모두 가진 C++의 강력한 도구이며, 현대 C++에서는 람다를 통해 더 직관적으로 사용된다.

반응형