추가 글 : 함수 객체 (Fuction Object) 템플릿 / 람다식 — A Game Client Programmer (tistory.com)
콜백 메커니즘
개념을 설명하기 위해선 '서버 코드'와 '클라이언트 코드'의 개념이 필요하다.
서버 코드: 기능이나 서비스를 제공하는 코드
클라이언트 코드: '서버 코드'가 제공해주는 기능이나 서비스를 사용하는 코드
Print() 함수는 출력 기능을 제공하는 '서버 코드'다. main() 함수는 Print() 함수를 호출해 출력 기능을 사용하는 '클라이언트 코드'가 된다.
일반적으로 '클라이언트 코드'에서 '서버 코드'를 호출하고 기능을 사용하지만, 반대로 '서버 코드'에서 '클라이언트 코드'를 호출하는 형태도 가능하다. '클라이언트 코드'에서 '서버 코드'를 호출하는 것을 '콜(call)'이라 하며, 반대로 '서버 코드'에서 '클라이언트 코드'를 호출하는 것을 '콜백(callback)'이라고 한다.
// 서버 코드
void Print() {
cout << "Server Code!" << endl;
}
// 클라이언트 코드
void main() {
Print();
}
실제로 '서버 코드'에서 '클라이언트 코드'를 직접적으로 호출하는 것은 불가능하다. 왜냐하면, '서버 코드'는 기능을 제공해주는 코드이고 '클라이언트 코드'는 서버에서 제공해주는 기능을 사용하는 코드인데, '서버 코드'에서는 클라이언트가 어떤 식으로 사용할지 미리 예측할 수 없기 때문에 '함수 포인터를 이용한 콜백 메커니즘'을 사용한다. (콜백 메커니즘을 구현하는데 사용하는 방법은 '함수 포인터' 외에도 '함수 객체', '대리자', '전략 패턴' 등이 있다.
// 서버 코드
void Print() {
cout << "Server Code!" << endl;
Client(); // '서버 코드'에서 '클라이언트 코드'를 호출
}
// 클라이언트 코드
void Client() {
cout << "Client Code!" << endl;
}
void main() {
Print(); // '서버 코드'를 호출
}
// 서버 코드
// 배열의 모든 원소에 반복적인 작업을 수행하도록 추상화
void For_each(int* begin, int* end, void(*pf)(int)) {
while (begin != end)
{
pf(*begin++); // '서버 코드'에서 '클라이언트 코드'를 호출 (콜백)
}
}
// 클라이언트 코드1
void Print1(int n) {
cout << n << endl;
}
// 클라이언트 코드2
void Print2(int n) {
cout << n + n << endl;
}
// 클라이언트 코드3
void Print3(int n) {
cout << n * n << endl;
}
void main() {
int arr[5] = {10, 20, 30, 40, 50};
For_each(arr, arr+5, Print1); // Print1() 함수의 주소를 전달
For_each(arr, arr+5, Print2); // Print2() 함수의 주소를 전달
For_each(arr, arr+5, Print3); // Print3() 함수의 주소를 전달
}
함수 객체
함수처럼 호출 가능한 클래스 객체다. 클래스 객체이기 때문에 클래스가 가지는 장점을 모두 이용하면서 함수처럼 사용할 수 있다 즉 클래스 객체를 함수 포인터로 넘길 수 있다. 그리고 '함수 객체'는 'Functor'라고 많이들 부른다. (Function Object의 약자).
struct Functor {
void operator()() {
cout << "Functor!" << endl;
}
}
void main() {
Functor functor;
functor(); // 해석하면 operator()()이란 '멤버 함수'를 호출
}
당연히 매개변수를 가질 수도 있다.
struct Functor {
void operator()(int a, int b) {
cout << "Functor!" << endl;
cout << a << ", " << b << endl;
}
}
void main() {
Functor functor;
functor(5, 10); // 매개변수를 가지고 호출
}
함수 객체(Functor)의 장점을 조금 더 끌어올려보자
class Adder {
private:
int total;
public:
explicit Adder(int n = 0) : total{n} { /* do somthing */ }
~Adder() { /* do somthing */ }
int operator()(int n)
{
return total += n;
}
}
void main() {
Adder add{0};
cout << add(10) << endl;
cout << add(20) << endl;
cout << add(30) << endl;
}
함수 포인터 객체를 이용한 개인 코드 출력 결과 15, 7
#include <iostream>
#include <functional>
using namespace std;
class Functor
{
public:
void operator()(int a = 0, int b = 0)
{
cout << a + b << endl;
}
};
void Fn(int a, int b)
{
cout << a + b << endl;
}
int main()
{
Functor functor;
// 함수 포인터 객체 방식
void (Functor::*ptr)(int,int) = &Functor::operator();
(functor.*ptr)(7, 8);
// std function 객체 방식
function<void(int, int)> ptr2 = Functor();
ptr2(3, 4);
}
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 공용체(union) 개념과 통신에서의 사용 이유 (0) | 2022.11.10 |
---|---|
C++ struct(구조체), union(공용체) 크기에 대한 정리 (0) | 2022.11.10 |
C++ 클래스 접근제한자 관련 보충 내용 (0) | 2022.11.07 |
C++ 객체 이동 std::move (0) | 2022.11.03 |
C++ 해시(Hash)의 의미 그리고 구현 (0) | 2022.10.31 |