icon안동민 개발노트

함수 객체


함수 객체의 개념

 함수 객체(Function Object) 또는 함수자(Functor)는 함수처럼 동작하는 객체를 말합니다.

 C++에서는 operator() 연산자를 오버로딩하여 함수 객체를 구현합니다.

 함수 객체의 주요 특징

  1. 함수처럼 호출 가능
  2. 상태를 가질 수 있음
  3. 컴파일 시간에 타입 체크 가능
  4. 인라인화 가능
  5. STL 알고리즘과 함께 사용 가능

함수 객체의 기본 구현

 다음은 간단한 함수 객체의 예입니다.

#include <iostream>
 
class Adder {
public:
    int operator()(int a, int b) const {
        return a + b;
    }
};
 
int main() {
    Adder add;
    std::cout << add(3, 4) << std::endl;  // 출력: 7
    return 0;
}

 이 예제에서 Adder 클래스는 operator() 를 오버로딩하여 함수 객체로 동작합니다.

함수 객체와 STL 알고리즘

 STL 알고리즘은 함수 객체를 인자로 받아 유연한 동작을 구현할 수 있습니다.

 다음은 std::count_if 알고리즘과 함수 객체를 사용하는 예입니다.

#include <iostream>
#include <vector>
#include <algorithm>
 
class IsEven {
public:
    bool operator()(int n) const {
        return n % 2 == 0;
    }
};
 
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int count = std::count_if(vec.begin(), vec.end(), IsEven());
    std::cout << "Number of even elements: " << count << std::endl;
    return 0;
}

상태를 가진 함수 객체

 함수 객체의 강점 중 하나는 내부 상태를 가질 수 있다는 점입니다.

 이를 통해 호출 간에 정보를 유지할 수 있습니다.

#include <iostream>
 
class Accumulator {
private:
    int sum;
public:
    Accumulator() : sum(0) {}
    int operator()(int n) {
        return sum += n;
    }
    int getSum() const { return sum; }
};
 
int main() {
    Accumulator acc;
    std::cout << acc(10) << std::endl;  // 10
    std::cout << acc(20) << std::endl;  // 30
    std::cout << acc(30) << std::endl;  // 60
    std::cout << "Final sum: " << acc.getSum() << std::endl;  // 60
    return 0;
}

템플릿을 이용한 일반화된 함수 객체

 템플릿을 사용하면 다양한 타입에 대해 동작하는 함수 객체를 만들 수 있습니다.

#include <iostream>
#include <string>
 
template<typename T>
class Less {
public:
    bool operator()(const T& a, const T& b) const {
        return a < b;
    }
};
 
int main() {
    Less<int> lessInt;
    std::cout << "3 < 5: " << lessInt(3, 5) << std::endl;  // 1 (true)
 
    Less<std::string> lessString;
    std::cout << "apple < banana: " << lessString("apple", "banana") << std::endl;  // 1 (true)
 
    return 0;
}

바인더와 어댑터

 STL은 함수 객체를 변형하거나 조합하는 도구를 제공합니다.

 std::bind

 std::bind를 사용하여 함수의 인자를 부분적으로 고정할 수 있습니다.

#include <iostream>
#include <functional>
 
int multiply(int a, int b) {
    return a * b;
}
 
int main() {
    auto times2 = std::bind(multiply, std::placeholders::_1, 2);
    std::cout << "4 * 2 = " << times2(4) << std::endl;  // 8
    
    auto always42 = std::bind(multiply, 6, 7);
    std::cout << "6 * 7 = " << always42() << std::endl;  // 42
    
    return 0;
}

 std::not_fn

 std::not_fn을 사용하여 조건을 반전시킬 수 있습니다.

#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
 
bool isEven(int n) {
    return n % 2 == 0;
}
 
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    auto isOdd = std::not_fn(isEven);
    int count = std::count_if(vec.begin(), vec.end(), isOdd);
    std::cout << "Number of odd elements: " << count << std::endl;
    return 0;
}

람다 표현식과 함수 객체

 C++ 11부터 도입된 람다 표현식은 익명 함수 객체를 쉽게 만들 수 있게 해줍니다.

#include <iostream>
#include <vector>
#include <algorithm>
 
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    
    std::for_each(vec.begin(), vec.end(), [](int& n) { n *= 2; });
    
    std::cout << "Doubled values: ";
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

 람다 표현식은 내부적으로 함수 객체로 변환됩니다.

실습 : 사용자 정의 정렬 기준

 다음 요구사항을 만족하는 프로그램을 작성해보세요.

  1. Person 구조체 정의 (이름, 나이)
  2. 여러 Person 객체를 벡터에 저장
  3. 나이를 기준으로 정렬하는 함수 객체 구현
  4. 이름을 기준으로 정렬하는 람다 표현식 작성
  5. 두 가지 방법으로 벡터 정렬 및 결과 출력
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
 
struct Person {
    std::string name;
    int age;
    
    Person(const std::string& n, int a) : name(n), age(a) {}
};
 
class AgeComparator {
public:
    bool operator()(const Person& a, const Person& b) const {
        return a.age < b.age;
    }
};
 
int main() {
    std::vector<Person> people = {
        {"Alice", 30}, {"Bob", 25}, {"Charlie", 35}, {"David", 28}
    };
    
    // 나이 기준 정렬
    std::sort(people.begin(), people.end(), AgeComparator());
    
    std::cout << "Sorted by age:" << std::endl;
    for (const auto& person : people) {
        std::cout << person.name << ": " << person.age << std::endl;
    }
    
    // 이름 기준 정렬
    std::sort(people.begin(), people.end(), 
              [](const Person& a, const Person& b) { return a.name < b.name; });
    
    std::cout << "\nSorted by name:" << std::endl;
    for (const auto& person : people) {
        std::cout << person.name << ": " << person.age << std::endl;
    }
    
    return 0;
}

연습 문제

  1. std::transform과 함수 객체를 사용하여 벡터의 모든 요소를 제곱하는 프로그램을 작성하세요.
  2. 람다 표현식을 사용하여 std::remove_if로 벡터에서 특정 조건을 만족하는 요소를 제거하는 프로그램을 작성하세요.
  3. 함수 객체를 사용하여 std::sort로 벡터를 내림차순으로 정렬하는 프로그램을 작성하세요.

 참고자료

  • "Effective STL" by Scott Meyers (항목 38-42 : 함수자에 대한 내용)
  • C++ Reference : Function objects
  • "Modern C++ Design" by Andrei Alexandrescu (Chapter 5 : Generalized Functors)
  • "C++ Templates : The Complete Guide" by David Vandevoorde and Nicolai M. Josuttis (Chapter 22: Bridging Static and Dynamic Polymorphism)
  • "Functional Programming in C++" by Ivan Čukić