컴퓨터공학/C++

[C++] 함수 객체, 콜백 함수

Pyxis 2024. 11. 19. 20:05

[ 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