메뉴 닫기

STL 함수 객체(Functor), 알고리즘 속 유연한 함수처럼 쓰이는 클래스


STL 함수 객체(Functor), 알고리즘 속 유연한 함수처럼 쓰이는 클래스

⚙️ C++ STL 알고리즘에서 자주 만나는 함수 객체, 제대로 이해하고 써봅시다

C++을 공부하다 보면 STL(Standard Template Library) 알고리즘에서 함수 객체(Functor)라는 단어를 자주 접하게 됩니다.
처음에는 생소하게 느껴질 수 있지만, 알고 보면 함수 객체는 우리가 흔히 사용하는 함수처럼 동작하면서도 더 많은 기능을 담을 수 있는 아주 유용한 도구입니다.
특히 STL 알고리즘에 사용자 정의 조건을 전달할 때 자주 쓰이는데요.
이 글에서는 함수 객체가 무엇인지, 왜 사용하는지, 실제로 어떤 상황에서 유용하게 활용되는지 차근차근 알아보려 합니다.
지금부터 함께 살펴보시죠.

여러분은 C++에서 함수처럼 동작하는 클래스를 만들어 본 적 있으신가요?
단순한 비교자부터 복잡한 조건 로직까지, 함수 객체는 STL의 다양한 알고리즘에서 유연하고 효율적인 방식으로 활용됩니다.
오늘은 이 operator() 오버로딩 기반의 함수 객체가 어떤 구조로 동작하고, 실제로 어떤 문제 해결에 도움을 주는지 직접 예제와 함께 알아보겠습니다.







🔗 함수 객체(Functor)란?

함수 객체(Functor)는 함수처럼 동작하는 클래스를 의미합니다.
좀 더 정확히 말하자면, operator() 연산자를 오버로딩한 클래스를 의미하며, 해당 객체를 함수 호출 문법으로 사용할 수 있습니다.

일반 함수는 독립적으로 정의되지만, 함수 객체는 클래스이기 때문에 상태(state)를 가질 수 있고, 다양한 방식으로 동작을 커스터마이징할 수 있다는 장점이 있습니다.
또한 템플릿과 조합했을 때 유연성과 재사용성이 뛰어나 STL 알고리즘과 찰떡궁합을 자랑합니다.

💬 Functor는 C++의 객체 지향적 기능을 활용해 함수 호출의 유연성과 기능 확장을 가능하게 하는 핵심 도구입니다.

다음은 함수 객체의 가장 단순한 형태입니다.
클래스 내부에 operator()를 정의해주면, 해당 객체를 함수처럼 호출할 수 있게 됩니다.

CODE BLOCK
#include <iostream>
using namespace std;

class Adder {
public:
    int operator()(int a, int b) {
        return a + b;
    }
};

int main() {
    Adder add;
    cout << add(3, 5) << endl; // 출력: 8
    return 0;
}

위 코드에서 Adder 클래스는 함수처럼 동작하는 객체입니다.
add(3, 5)와 같이 호출할 수 있는 이유는 바로 operator()가 정의되어 있기 때문입니다.
이러한 객체를 STL 알고리즘에 전달하면, 알고리즘이 해당 객체를 함수처럼 호출하며 원하는 작업을 수행하게 됩니다.

정리하자면, 함수 객체는 클래스를 통해 함수처럼 동작하는 인터페이스를 만드는 기법이며, C++의 유연하고 강력한 문법 중 하나로 널리 활용됩니다.
다음 장에서는 이러한 Functor가 어떻게 구성되어 있는지 좀 더 구체적으로 살펴보겠습니다.


🛠️ 함수 객체의 기본 구조와 operator() 오버로딩

함수 객체(Functor)의 핵심은 operator()를 오버로딩하는 데 있습니다.
이 연산자를 오버로딩하면 객체를 함수처럼 사용할 수 있으며, 이는 함수 포인터와 유사한 방식으로 동작합니다.
하지만 단순한 함수와는 다르게 객체가 상태(state)를 가질 수 있다는 점이 가장 큰 차이점입니다.

함수 객체는 다음과 같은 구조로 정의됩니다.
매개변수와 반환값을 자유롭게 구성할 수 있으며, 클래스의 멤버 변수나 외부 조건에 따라 동적으로 동작할 수 있습니다.

CODE BLOCK
class Multiply {
private:
    int factor;
public:
    Multiply(int f) : factor(f) {}

    int operator()(int x) const {
        return x * factor;
    }
};

이 예제에서 Multiply 클래스는 생성자를 통해 배수를 설정하고, operator()를 통해 입력값과 곱해주는 역할을 합니다.
즉, 함수 객체 내부에 설정된 상태에 따라 동일한 입력도 다른 결과를 낼 수 있는 함수로 작동합니다.

💎 핵심 포인트:
operator()는 클래스 내부에서 오버로딩하여 객체가 마치 함수처럼 호출되게 만듭니다. 이를 통해 상태를 가진 유연한 함수형 구조를 구현할 수 있습니다.

이러한 구조는 특히 STL 알고리즘에서 사용자 정의 조건을 전달할 때 매우 유용합니다.
함수 객체는 상태 유지가 가능하기 때문에 람다 함수나 일반 함수 포인터보다 복잡한 조건 처리를 손쉽게 구현할 수 있습니다.

예를 들어, 특정 기준 이상인 값만 필터링하거나, 정렬 기준을 동적으로 설정하는 등의 작업에 함수 객체가 자주 활용됩니다.
이제 다음 단계에서는 STL 알고리즘과 함께 실제로 함수 객체가 어떻게 활용되는지 예시를 통해 알아보겠습니다.







⚙️ STL 알고리즘에서의 함수 객체 활용 예시

함수 객체(Functor)는 STL 알고리즘에 조건을 전달할 때 자주 사용됩니다.
특히 sort, for_each, find_if 같은 알고리즘 함수에서는 사용자가 직접 정의한 조건식을 함수 객체로 구현하면 훨씬 더 깔끔하고 재사용성 높은 코드를 만들 수 있습니다.

대표적인 예로, sort 함수에서 사용자 정의 비교 기준을 제공하는 경우를 들 수 있습니다.
예제를 통해 직접 확인해보세요.

CODE BLOCK
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class Descending {
public:
    bool operator()(int a, int b) {
        return a > b;
    }
};

int main() {
    vector<int> numbers = {4, 1, 7, 3, 9};

    sort(numbers.begin(), numbers.end(), Descending());

    for (int n : numbers) {
        cout << n << " ";
    }
    return 0;
}

위 예제에서 Descending 함수 객체는 정렬 시 두 값을 비교해 내림차순으로 정렬되도록 합니다.
sort 함수에 직접 비교 조건을 전달할 수 있기 때문에, 로직이 깔끔하고 유연하게 유지됩니다.

  • sort에서 사용자 정의 정렬 조건 제공
  • 🔍find_if로 조건에 맞는 원소 찾기
  • 🔁for_each를 통한 조건 반복 처리

함수 객체는 단순한 비교뿐 아니라, 입력 값에 따라 다르게 동작하는 조건이나 외부 설정에 따라 동작이 바뀌는 필터링에도 사용할 수 있습니다.
이처럼 함수 객체는 C++ STL의 핵심 도구로, 알고리즘과 함께 사용할 때 그 진가를 발휘합니다.


🔌 함수 객체 vs 람다 함수 차이점

함수 객체(Functor)와 람다 함수는 모두 함수처럼 동작하는 개체라는 공통점을 갖고 있지만, 사용 목적과 구조에는 분명한 차이가 존재합니다.
두 방식 모두 STL 알고리즘에 전달할 수 있지만, 각각의 장단점을 이해하고 적절하게 선택하는 것이 중요합니다.

⚙️ 선언과 사용의 간결성

람다 함수는 코드 내에서 간단하게 선언하고 바로 사용할 수 있는 문법을 제공합니다.
복잡한 클래스 선언 없이 즉석에서 조건이나 동작을 정의할 수 있어, 코드가 간결하고 가독성이 높습니다.

CODE BLOCK
sort(vec.begin(), vec.end(), [](int a, int b) {
    return a > b;
});

위와 같이 람다를 쓰면, 별도의 클래스 정의 없이도 직관적인 비교 조건을 전달할 수 있어 매우 편리합니다.

🔧 확장성과 재사용성

반면, 함수 객체는 구조화된 클래스로 되어 있기 때문에 복잡한 상태 관리나 여러 알고리즘에서 반복적으로 사용하는 경우에 훨씬 유리합니다.
상태를 멤버 변수로 저장하거나, 여러 함수에서 재사용할 수 있는 논리를 담을 수 있다는 점에서 확장성이 뛰어납니다.

💎 핵심 비교:
람다 함수는 간단하고 빠르게 쓰기에 적합하고, 함수 객체는 복잡한 상태와 반복 활용이 필요한 경우에 적합합니다.

항목 람다 함수 함수 객체
선언 위치 함수 내부에서 바로 선언 별도 클래스 정의 필요
상태 저장 불가능 또는 제한적 가능
재사용성 낮음 높음

결론적으로, 간단한 작업에는 람다 함수를,
복잡하고 확장 가능한 구조에는 함수 객체를 사용하는 것이 효율적입니다.
두 도구를 목적에 맞게 활용한다면 STL 알고리즘을 더욱 강력하게 사용할 수 있습니다.







💡 커스터마이징이 쉬운 Functor의 장점

함수 객체(Functor)의 가장 큰 장점은 사용자 정의 커스터마이징이 매우 용이하다는 것입니다.
클래스 기반으로 구현되기 때문에, 다양한 멤버 변수와 메서드를 포함시킬 수 있으며, 복잡한 조건 처리나 상태 유지가 필요한 로직에서도 높은 유연성을 발휘합니다.

예를 들어, 조건을 기반으로 동작하는 필터를 구현할 때 함수 객체는 단순한 람다나 함수 포인터보다 훨씬 깔끔하고 체계적으로 구성할 수 있습니다.
아래는 임계값(threshold)을 기준으로 조건을 검사하는 함수 객체 예시입니다.

CODE BLOCK
class GreaterThan {
private:
    int threshold;
public:
    GreaterThan(int t) : threshold(t) {}

    bool operator()(int value) const {
        return value > threshold;
    }
};

이 함수 객체는 생성 시 임계값을 설정하고, operator()를 통해 해당 조건을 평가합니다.
이 구조는 STL의 find_if, remove_if, count_if 등 조건 기반 알고리즘과 함께 사용할 때 매우 유용합니다.

💡 TIP: 함수 객체는 클래스 기반이기 때문에, 멤버 변수와 메서드를 조합해 다양한 로직을 캡슐화하고 구조화할 수 있습니다. 이는 복잡한 알고리즘을 깔끔하게 유지하는 데 큰 도움이 됩니다.

또한 함수 객체는 템플릿과의 궁합도 뛰어납니다.
하나의 Functor 클래스를 템플릿으로 설계하면, 타입에 관계없이 범용적인 비교자, 필터, 계산 로직을 만들 수 있어 코드 재사용성과 유지보수가 더욱 쉬워집니다.

이처럼 함수 객체는 단순히 ‘함수처럼 동작하는 객체’를 넘어서, 상태와 로직을 함께 갖춘 유연한 도구로서 STL을 더욱 강력하게 만드는 중요한 개념입니다.


❓ 자주 묻는 질문 (FAQ)

함수 객체와 일반 함수의 가장 큰 차이점은 무엇인가요?
일반 함수는 상태를 저장할 수 없지만, 함수 객체는 클래스 기반이라 멤버 변수로 상태를 보존할 수 있다는 점이 가장 큰 차이입니다.
함수 객체는 STL의 어떤 알고리즘에서 자주 사용되나요?
sort, find_if, count_if, for_each, remove_if 등 조건을 받아들이는 다양한 알고리즘에서 자주 사용됩니다.
람다 함수와 비교했을 때 Functor의 장점은 무엇인가요?
Functor는 클래스 형태라 멤버 변수를 이용한 상태 유지가 가능하고, 재사용성과 확장성이 뛰어납니다. 복잡한 로직을 캡슐화하기에 적합합니다.
Functor를 꼭 class로만 만들어야 하나요?
struct를 사용해도 동일하게 operator()를 오버로딩할 수 있습니다. 단, 멤버 접근 제어가 필요하다면 class 사용이 더 유리합니다.
템플릿과 함께 사용하는 함수 객체는 어떻게 구현하나요?
템플릿을 클래스에 적용하면, 다양한 타입에 대응하는 범용적인 함수 객체를 만들 수 있어 STL과 결합 시 더 큰 유연성을 가집니다.
Functor는 함수 포인터와 호환되나요?
직접적으로는 아니지만, std::function을 사용하면 함수 객체, 람다, 함수 포인터를 모두 통합적으로 다룰 수 있습니다.
STL 내부에도 함수 객체가 정의되어 있나요?
네, std::less, std::greater, std::equal_to 등의 표준 비교자 Functor가 이미 STL 내에 구현되어 있습니다.
operator() 외에도 오버로딩 가능한 연산자가 있나요?
물론입니다. C++에서는 operator+, operator==, operator< 등 다양한 연산자를 오버로딩할 수 있으며, 그 중 함수 호출 연산자(operator())는 함수 객체의 핵심입니다.



🧠 STL 알고리즘에서 함수 객체를 활용하는 진짜 이유

함수 객체(Functor)는 단순히 함수처럼 호출되는 객체 이상의 역할을 합니다.
C++ STL 알고리즘과 결합하면 더욱 강력한 성능과 구조적 유연성을 제공하며, 코드의 재사용성과 유지보수성을 동시에 높여줍니다.

특히 operator()를 오버로딩하여 동작하는 구조는 람다 함수와 비교했을 때 상태를 가질 수 있다는 점에서 강력한 장점을 가집니다.
정렬, 검색, 필터링 등 다양한 STL 알고리즘에서 조건을 커스터마이징할 수 있고, 템플릿과 결합하면 범용적인 설계도 가능합니다.

이번 글에서는 함수 객체의 기본 구조부터 람다와의 비교, 실제 STL 활용 사례까지 모두 짚어보았습니다.
이제 여러분도 STL과 Functor를 조합해 더 깔끔하고 확장 가능한 C++ 코드를 작성하실 수 있을 거예요.
실무나 알고리즘 문제 풀이에 있어 중요한 무기가 될 수 있으니, 꼭 직접 구현해보고 익혀보시길 추천드립니다.


🏷️ 관련 태그 : STL, C++기초, 함수객체, Functor, C++알고리즘, 람다함수비교, operator오버로딩, C++정렬, 함수형프로그래밍, 템플릿활용