본문 바로가기
SW/C,C++

[C/C++] C++ 에서의 콜백함수

by FastBench 2024. 4. 7.

C++에서 콜백 함수의 사용

C++에서 콜백 함수는 여전히 중요하며, C언어에서의 사용법을 기반으로 더 다양하고 강력한 방법으로 발전하였다.

기본적으로, C++에서는 함수 포인터, 함수 객체, 람다 표현식 등을 통해 콜백 함수를 구현할 수 있다.

 

  1. 함수 포인터: C언어의 콜백 함수 구현 방식과 유사하게, C++에서도 함수 포인터를 사용해 콜백을 구현할 수 있다. 하지만, 이 방법은 객체 지향적 특성을 충분히 활용하지 못하는 단점이 있다.
  2. 함수 객체 (Function Objects 또는 Functors): C++에서는 객체를 함수처럼 호출할 수 있는 함수 객체를 사용하여 콜백을 구현할 수 있다. 이는 클래스 내에 operator()를 오버로딩함으로써 가능하다. 이 방법은 상태를 유지할 수 있는 장점이 있으며, 객체 지향적 설계에 더 적합하다.
  3. 람다 표현식: 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++ 프로그래밍에서 콜백을 더 유연하고 강력하게 만들어 준다.

댓글