C++에서 콜백 함수의 사용
C++에서 콜백 함수는 여전히 중요하며, C언어에서의 사용법을 기반으로 더 다양하고 강력한 방법으로 발전하였다.
기본적으로, C++에서는 함수 포인터, 함수 객체, 람다 표현식 등을 통해 콜백 함수를 구현할 수 있다.
- 함수 포인터: C언어의 콜백 함수 구현 방식과 유사하게, C++에서도 함수 포인터를 사용해 콜백을 구현할 수 있다. 하지만, 이 방법은 객체 지향적 특성을 충분히 활용하지 못하는 단점이 있다.
- 함수 객체 (Function Objects 또는 Functors): C++에서는 객체를 함수처럼 호출할 수 있는 함수 객체를 사용하여 콜백을 구현할 수 있다. 이는 클래스 내에 operator()를 오버로딩함으로써 가능하다. 이 방법은 상태를 유지할 수 있는 장점이 있으며, 객체 지향적 설계에 더 적합하다.
- 람다 표현식: C++11부터 도입된 람다 표현식은 익명 함수를 간결하게 작성할 수 있게 해주며, 콜백 함수를 구현하는 데 매우 유용하다. 람다는 캡처 리스트를 통해 외부 변수를 사용할 수 있으며, 강력하고 표현력이 뛰어난 콜백 구현 방식을 제공한다.
C++의 람다 표현식을 사용한 콜백 함수 구현 예제
이 예제에서는 std::sort 함수를 사용하여 사용자 정의 정렬 기준에 따라 배열을 정렬하는 방법을 보여준다.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {4, 2, 5, 1, 3};
// 람다 표현식을 사용한 콜백 함수
std::sort(numbers.begin(), numbers.end(), [](int a, int b) {
return a < b; // 오름차순 정렬
});
// 정렬된 배열 출력
for (int number : numbers) {
std::cout << number << ' ';
}
return 0;
}
위 예제에서는 std::sort 함수의 세 번째 인자로 람다 표현식을 전달하여 콜백 함수로 사용하고 있다.
람다 표현식 [](int a, int b) { return a < b; }는 두 인자를 비교하여 오름차순으로 정렬하는 기준을 정의한다.
람다 표현식을 사용함으로써 콜백 함수를 간결하고 직관적으로 작성할 수 있다.
함수 객체를 사용하여 상태를 유지하는 콜백 함수 구현 예제
C++에서 operator()를 오버로딩하는 기능을 통해 클래스의 인스턴스를 함수처럼 호출할 수 있게 된다. 이를 통해 생성된 인스턴스를 "함수 객체" 또는 "펑터(functor)"라고 한다. operator()를 오버로딩함으로써 클래스는 다양한 매개변수와 반환 타입을 가진 호출 가능한 객체로 작동할 수 있으며, 이는 함수 포인터보다 더 유연한 사용을 가능하게 한다.
class Functor {
public:
반환_타입 operator()(매개변수_타입1 매개변수1, 매개변수_타입2 매개변수2, ...) {
// 함수처럼 동작할 코드
}
};
함수 객체를 사용하면 콜백 함수 내에서 상태를 유지할 수 있다.
이를 통해 멤버 변수로 count 를 넣고, operator() 오버로딩 시 count 가 증가하도록 선언하면, 반복 호출될 때마다 count값이 증가하는(변수의 상태가 유지되는) 콜백 함수를 구현할 수 있다.
#include <iostream>
// 콜백을 위한 함수 객체 클래스
class Callback {
public:
Callback() : count(0) {}
// 호출될 때마다 내부 카운트를 증가시킴
void operator()() {
std::cout << "Callback function was called. Count: " << ++count << std::endl;
}
int getCount() const {
return count;
}
private:
int count;
};
// 콜백 함수 객체를 받아서 호출하는 함수
void triggerEvent(Callback& callback) {
callback(); // 여기서 callback은 원본 객체의 메서드를 호출
}
int main() {
Callback myCallback; // 콜백 함수 객체 생성
// 이벤트 트리거
triggerEvent(myCallback); // 첫 번째 이벤트 발생, 내부 카운트 증가
triggerEvent(myCallback); // 두 번째 이벤트 발생, 내부 카운트 증가
// 최종 카운트 출력
std::cout << "Total callback count: " << myCallback.getCount() << std::endl;
return 0;
}
위 예제에서 Callback 클래스는 operator()를 오버로딩하여 함수 객체로 사용된다. 이 클래스는 내부적으로 count 멤버 변수를 통해 호출이 수행된 횟수를 추적한다.
주의 할점은, triggerEvent 함수에서 reference 로 callback 클래스를 받지 않는다면, 함수객체의 멤버변수 변화는 일어나지 않는다. 매번 객체의 복사본이 전달될 것이기 때문이다.
따라서, std::sort 와 같은 표준 라이브러리에서 함수객체를 콜백함수로 사용하면 counting 이 정상적으로 이루어 지지 않는다. 아래의 코드는 잘 동작할 것 같지만, 실제로 Comparison count 는 0 이 나온다.
#include <iostream>
#include <vector>
#include <algorithm>
class CountingComparator {
public:
CountingComparator() : count(0) {}
// 함수 객체를 호출하는 연산자 오버로딩
bool operator()(int a, int b) {
++count; // 비교 연산 횟수 증가
return a < b; // 오름차순 정렬 기준
}
// 비교 횟수를 반환하는 함수
int getCount() const {
return count;
}
private:
int count; // 비교 연산 횟수를 저장할 멤버 변수
};
int main() {
std::vector<int> numbers = {4, 2, 5, 1, 3};
CountingComparator comp;
// 함수 객체 comp를 사용하여 정렬
std::sort(numbers.begin(), numbers.end(), comp);
// 비교 횟수 출력
std::cout << "Comparison count: " << comp.getCount() << std::endl;
return 0;
}
결론
C++에서 콜백 함수의 사용은 C언어에서의 사용법을 기반으로 하면서도 객체 지향적 특성, 람다 표현식, 함수 객체 등을 통해 더 발전되고 풍부해졌다. 이러한 다양한 접근 방법은 C++ 프로그래밍에서 콜백을 더 유연하고 강력하게 만들어 준다.
'SW > C,C++' 카테고리의 다른 글
[C/C++] 콜백함수의 인자로써 void 포인터 (0) | 2024.04.21 |
---|---|
[C/C++] 이중포인터와 void 포인터 (0) | 2024.04.20 |
[C/C++] 동적 2차원 배열과 가변길이배열(VLA) (0) | 2024.04.11 |
[C/C++] 콜백함수 (Callback Function) (0) | 2024.04.01 |
[C/C++] C언어에서의 객체지향 (0) | 2024.03.31 |
댓글