함수처럼 호출할 수 있는 객체
클래스 내부에 연산자 오버로딩을 통해 () 연산자를 구현한 클래스의 인스턴스
struct Adder
{
int operator()(int a, int b) const
{
return a + b;
}
};
int main()
{
Adder add; // 객체 인스턴스 생성
int result = add(10, 20); // 객체를 함수처럼 사용
std::cout << "결과: " << result << std::endl; // 30
}
template <typename T, typename Compare>
void sort(T* arr, int size, Compare comp)
{
// ... 정렬 로직 ...
if (comp(arr[i], arr[j]))
{
swap(arr[i], arr[j]);
}
}
템플릿을 이용하여, 매개변수로 함수를 받아 사용 가능 (함수 포인터처럼)
심지어 함수 포인터보다 좋은데, 이유는 아래의 2가지가 있음
class Incrementer
{
private:
int count;
public:
Incrementer(int start) : count(start) {}
int operator()()
{
return ++count;
}
};
int main()
{
Incrementer Inc10(10);
Incrementer Inc20(20);
cout << Inc10() << " " << Inc20(); // 11 21
}
일반 함수는 전역변수나 static변수로 내부 상태를 저장할 수는 있으나, 전역변수는 단점이 많고, static은 같은 함수는 공유하는 성질을 가짐
함수객체는 클래스의 멤버변수를 통해 값을 저장할 수 있어, 여러 객체를 만들듯이 여러 함수객체를 만들어 상태 저장 가능
sort같이 비교함수를 받고 싶을 때, 함수포인터를 이용해 받거나 함수 객체를 이용해 받을 수 있음
sort는 다양한 함수를 받을 수 있어야 하는데, 어떤 함수가 들어오는지는 함수포인터의 경우 런타임에 정해지고, 함수객체의 경우 컴파일 타임에 정해짐
// c의 qsort
void sort(int* arr, int size, bool (*compare)(int, int)) // 함수 포인터로 받음
{
// ... 정렬 로직 ...
if (compare(arr[i], arr[j])) { // 실행 시점에 주소를 찾아가서 물어봄 (오버헤드 발생)
swap(arr[i], arr[j]);
}
}
함수포인터의 경우 런타임에 정해지므로 컴파일러는 그냥 해당 주소로 점프하도록 컴파일만 해줌. 따라서 인라인 못 해서 최적화 안 됨
그러나 함수 객체는 템플릿을 이용해 컴파일 타임에 타입이 결정되므로 컴파일러가 호출 코드를 직접 삽입하여(인라인) 최적화 가능
template <typename T, typename Compare>
void sort(T* arr, int size, Compare comp)
{
// ... 정렬 로직 ...
if (comp(arr[i], arr[j])) { // 컴파일 타임에 comp가 뭔지 이미 앎 (코드 직접 삽입)
swap(arr[i], arr[j]);
}
}
sort 알고리즘, map/set/priority_queue 같은 자료구조에 정렬 기준으로 사용 가능struct Descending
{
bool operator()(int a, int b) const
{
return a > b; // 내림차순 정렬 기준
}
};
sort(v.begin(), v.end(), Descending()); // 함수 객체 전달
struct BookCompare
{
bool operator()(const Book& a, const Book& b) const // const 붙여야함
{
// ...
}
};
set<Book, BookCompare> library;
map<Book, int, BookCompare> rentCounts;
priority_queue<Book, vector<Book>, BookCompare> pq;
복잡성
단순히 두 수를 더하는 기능이 필요한데, 이를 위해 클래스를 선언하고, 멤버 변수를 고민하고, operator()를 오버로딩하는 과정은 불필요한 복잡성만 증가시킴
메모리 증가
함수 객체는 결국 객체이므로, 메모리에 각 인스턴스가 공간을 차지하게 됨. 일반 함수는 메모리 코드 영역에서 하나만 존재.
객체 지향 오버헤드
상태가 필요 없는 순수 함수인데 객체로 만들면, 불필요한 데이터 멤버나 가상 함수 테이블(사용 시) 등의 설계 고민이 뒤따름. 또한, 사용 시 꼭 객체를 생성해야하고 사용해야함
lambda를 사용하는 경우가 많음operator() 여러개 오버로딩 가능struct MultiFunction
{
// 정수 두 개를 더함
int operator()(int a, int b) const
{
return a + b;
}
// 문자열 두 개를 합침
std::string operator()(const std::string& a, const std::string& b) const
{
return a + b;
}
// 인자 하나만 받는 경우
void operator()(double a) const
{
std::cout << "실수 값: " << a << std::endl;
}
};
struct GenericPrinter
{
template <typename T>
void operator()(T message) const
{
std::cout << "출력: " << message << std::endl;
}
};
struct Adder
{
int num;
Adder(int a, int b)
{
num = a + b;
}
int operator()(int c)
{
num += c;
return num;
}
};
int result = Adder(10, 20)(30);