[ C++ : 함수 객체, 콜백 함수 ]
함수 포인터 리마인드
void (*pfunc)(void);
포인터를 타고 갔더니 데이터가 아니라 함수가 있는 것.
동작을 넘겨줄 때 유용하다
pfunc = &HelloWorld;
(*pfunc)();
함수 포인터 단점
1) 시그니처가 안 맞으면 사용할 수 없다.
2) 상태를 가질 수 없다.
상태를 가질 수 없다는건 무슨 뜻일까?
일반적으로 객체지향 클래스를 만든다고 가정할 때, 데이터와 동작을 둘 다 들고 있을 수 있는데 Knight가 _hp를 갖는다면 이건 Knight의 상태 값이 된다.
→ 각기 다른 "상태"를 지닐 수 있다.
함수는 상태가 없다. Knight가 _hp를 갖고 있으면서 유지되는 개념이 없는 것이다.
하지만 경우에 따라서 기존에 실행했던, 혹은 저장한 데이터를 유지시키는게 유용한 상황이 생기는데 함수 포인터로는 불가능하지만, 함수 객체라는 것을 이용하면 극복가능하다.
[ 함수 객체 : Functor ]
함수처럼 동작하는 객체
함수처럼 동작한다 → "()" 연산자 오버로딩이 필요
class Functor
{
public:
operator()()
{
cout << "Functor Test" << "\n";
cout << _value << "\n";
}
void operator()(int num)
{
cout << "Functor Test" << "\n";
cout << num << "\n";
}
private:
int _value = 0;
}
/* ... */
Functor functor;
functor();
functor(16);
장점은, 클래스처럼 상태 값도 들고 있을 수 있으면서 일반 함수처럼 호출할 수 있는 것.
그리고 () 연산자의 여러 인자를 받는 오버로딩이 가능하다.
[ 용도 ]
MMO 서버에서 꽤나 쓰게 된다.
서버 : 클라가 보내준 네트워크 패킷을 받앗 ㅓ처리
ex) 클라 : 나 (5, 0) 좌표로 이동시켜줘
만약 몇천명이 패킷을 쏜다면 동시다발적 처리가 힘들어진다.
들어올 때 마다 처리하지 않고 들어온 순서대로 여유가 될 때 처리하게 될 것.
Functor를 만들어주게 되면 만들어주는 시점과 실행하는 시점을 분리할 수 있는 장점이 생기게 된다.
→ 커맨드 패턴
[ 콜백 함수 ]
콜백 함수 : 함수 포인터 + 함수 객체 + 템플릿
콜백(Call back) : 다시 호출하다, 역으로 호출하다
게임을 만들 때 이런 콜백의 개념이 자주 등장한다..
당장 특정 함수를 호출하지 않고, 미뤘다가 어떤 시점에 왔을 때 호출 하는 것.
어떤 상황이 일어나면, 이 기능을 호출해줘.
class Item
{
public:
public:
int _itemId = 0;
int _rarity = 0;
int _ownerId = 0;
};
class FindByOwnerId
{
public:
bool operator()(const Item* item)
{
return (item->_ownerId == _ownerId);
}
public:
int _ownerId;
};
class FindByOwnerRarity
{
public:
bool operator()(const Item* item)
{
return (item->_ownerId == _ownerId);
}
public:
int _ownerId;
};
template<typename T>
Item* FindItem(Item items[], int itemCount, T selector)
{
for (int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
if (selector(item))
return item;
return item;
}
return nullptr;
}
기존의 문제점은, FindItem에 인자로 함수 포인터를 넣어도 비교할려는 인자 개수가 함수 시그니처에 따라 제한적이었다. 특정 조건과 함께 특정 itemId같은 것도 같이 건네주고 싶어도 할 수 없었다.
상태를 가져서 인자를 유연하게 받아주는 함수 객체를 넣더라도, 일반화하여 여러종류의 함수객체를 넣을 수 없었다는 문제도 있다.
마지막에 배운 방법인 템플릿을 이용해서, 함수 객체의 타입을 T로 넣을 수 있게 된다.
/* ... */
int main()
{
/* ... */
Item items[10];
items[3]._ownerId = 100;
items[9]._rarity = 2;
FindByOwnerId functor1;
functor1._ownerId = 100;
FindByRarity functor2;
functor2._rarity = 2;
Item* item1 = FindItem(items, 10, functor1);
Item* item2 = FindItem(items, 10, functor2);
FindItem(items, 10, functor1);
}
이제 함수 객체를 만든 뒤, 템플릿을 이용해서 T타입 함수 객체를 인자로 받아준다면 유연하게 일반화된 조건을 받아서 실행할 수 있게 된다.
[ Reference ]
Rookiss, Part1 C++ 프로그래밍 입문
'컴퓨터공학 > C++' 카테고리의 다른 글
[More Effective C++] 다중 상속 (0) | 2024.11.17 |
---|---|
[C++] #include <algorithm> (0) | 2024.11.17 |
[C++] 함수 포인터 (0) | 2024.11.13 |
[C++] using (0) | 2024.11.09 |
[C++] noexcept (0) | 2024.11.09 |