STL을 공부하기 위한 사전지식
: 함수 포인터, 함수객체, 템플릿...
이번 시간에는 함수 포인터를 공부한다
함수 포인터도 결국 일반 포인터와 차이가 없기에
typedef로 정의 후, 포인터 변수로 이용하는 방식으로 사용해 보자
일반 포인터를 먼저
typedef로 정의 후, 포인터 변수로 이용해 보자
- typedef를 이용한 일반적인 변수 포인터
typedef int DATA; // typedef로 정의
DATA* pointer = &a; // 포인터 변수로 이용하기
위를 간단하게 나타내면
1) 포인터 *
2) 변수 이름 pointer
3) 데이터타입 int
그럼,
1] 함수를 typedef를 정의하고
2] 포인터 함수로 이용해 보자
- typedef를 함수에 사용해 보자
int func(int a, int b){}
라는 함수가 구현되어 있다고 하고
위 함수를 typedef로 정의해 보자
typedef int (int a, int b) FUNC_TYPE; // 잘못됨
위처럼 하면 되나 싶지만
FUNC_TYPE은 맨 뒤가 아닌 중간에 넣어야 한다
따라서
typedef int (FUNC_TYPE) (int a, int b); // 올바름
이렇게 만들면 된다
- 여기서 잠깐) using을 함수에 사용해 보자
using을 이용하면 typedef보다 쉽고 뜻도 분명해진다
using FUNC_TYPE = (int)(int a, int b);
다시 돌아와서
- 함수 포인터로 만들기
typedef int (FUNC_TYPE) (int a, int b);
FUNC_TYPE* fn;
1) 포인터 *
2) 변수 이름 fn
3) 데이터타입 함수 (인자는 int, int 반환은 int)
굳이 함수 포인터를 사용하는 이유?
1)
함수 포인터를 만들고 거기에 여러 함수를 넣어서 사용 가능하다
아래와 같이, 사용 가능하다
int Add(int a, int b)
{
return a+b;
}
int Sub(int a, int b)
{
return a-b;
}
int main()
{
int result = Add(1,2);
// 함수의 이름은 함수의 시작 주소를 가지고 있음 (배열과 유사)
typedef int(FUNC_TYPE) (int a, int b);
FUNC_TYPE* fn;
fn = Add(); // 이렇게 함수 포인터 자유자재로 사용 가능
result = fn(1,2); // 이렇게 사용 가능
result = (*fn)(1,2); // 함수 포인터는 *(접근연산자)를 붙여도 함수 주소를 가리킴
// 결국 위 아래는 똑같은 의미
// 그런데 굳이 이렇게 하는 이유는?
// 함수의 반환타입, 인자 개수와 타입이 같다면
// fn에 원하는 함수를 넣고, 자유자재로 사용 가능
// ex.
fn = Sub();
result = fn(1,2);
result = (*fn)(1,2);
}
2)
두 번째 이유는 아래 코드들을 통해 알아보자
class Item()
{
public:
Item() : _itemId(0), _rarity(0), _ownerId(0)
{
}
public:
int _itemId; // 아이템을 구분하기 위한 ID
int _rarity; // 희귀도
int _ownerId; // 소지자 ID
}
// 아이템을 찾는 함수를 만든다고 하면,
// _itemId를 고려한 아이템 찾는 함수를 만들고
Item* FindItemByItemId(Item items[], int itemCount, int itemId)
{
for(int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
if(item->_itemId == itemId)
{
return item;
}
}
return nullptr;
}
Item* FindItemByRarity(Item items[], int itemCount, int itemId){}
Item* FindItemByOwnerId(Item items[], int itemCount, int itemId){}
// _rarity를 고려한 아이템 찾는 함수를 만들고
// _ownerId를 고려한 아이템 찾는 함수를 만들고...
// 이렇게 사실상 비슷한데 여러개를 만들어 줘야함
// 심지어 수정하려면 모든 함수들 내부를 다 수정해야함
위의 상황은 함수 포인터를 사용하지 않은 경우이다
하지만,
아래와 같은 논리를 이해한 후, 함수 포인터를 이용해 보자
Item* FindItem(Item items[], int itemCount, // 여기에 동작을 넣고)
{
for(int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
// 조건이 아닌 동작을 넣어주는 형태로 만들어보자
return item;
}
return nullptr;
}
그럼, 위의 논리를 고려해 FindItem 함수의 인자에 함수 포인터를 받아와 보자
bool IsRareItem(Item* item)
{
return item-> _rarity >= 2;
}
Item* FindItem(Item items[], int itemCount, bool (*selector)(Item* item))
{
for(int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
// 이 부분은 아래에서...
return item;
}
return nullptr;
}
위에서 bool (*selector)(Item* item) 은 처음 보는 형태이다
사실상 이건
bool (*selector)(Item* item) // 이 부분은 사실상
typedef bool(SELECTOR_TYPE)(Item* item); // 함수의 타입을 typedef로 지정해주고
Item* FindItem(Item items[], int itemCount, SELECTOR_TYPE* selector// 함수의 포인터를 만들어준 것)
// 위의 과정을 생략한 과정이다
결국, 아래와 같이
함수 포인터를 인자로 받고 이 동작을 함수 내부의 조건으로 사용할 수 있다
bool IsRareItem(Item* item)
{
return item-> _rarity >= 2;
}
Item* FindItem(Item items[], int itemCount, SELECTOR_TYPE* selector) // 함수 포인터를 인자로 받음
{
for(int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
if(selector(item)) // 동작의 결과를 조건으로 받음
return item;
}
return nullptr;
}
int main()
{
Item items[10];
items[3]._rarity = 2;
Item* rareItem = FindItem(items, 10, isRareItem); // 이렇게 함수를 함수의 매개변수에 넣어줄 수 있다
}
그런데... 함수의 시그니쳐가 똑같지 않고 인자 개수가 다른 경우에는 아래처럼 사용이 불가능하다
bool IsRareItem(Item* item) // 얘는 그렇게 사용했지만
{
return item-> _rarity >= 2;
}
bool IsOwnerItem(Item* item, int ownerId) // 얘는 위 자리에 사용이 불가능...
{
return item->_ownerId == ownerId;
}
하지만, 이런 부분은 함수 객체, std::function, lambda를 이용하면 해결 가능하다
굳이 현재 상황에서 해결하고 싶다면, 아래와 같이 가능하긴 하다
typedef bool(SELECTOR_TYPE)(Item* , int); // 시그니쳐 동일하게
Item* FindItem(Item items[], int itemCount, SELECTOR_TYPE* selector, int value)
{
for(int i = 0; i < itemCount; i++)
{
Item* item = &items[i];
if(selector(item, value))
return item;
}
return nullptr;
}
bool IsRareItem(Item* item, int value) // 둘다 같은 시그니쳐 형태로 만들어줌
{
return item-> _rarity >= value;
}
bool IsOwnerItem(Item* item, int ownerId) // 둘다 같은 시그니쳐 형태로 만들어줌
{
return item->_ownerId == ownerId;
}
int main()
{
Item items[10];
items[3]._rarity = 2;
Item* rareItem = FindItem(items, 10, isRareItem, 2);
Item* ownerItem = FindItem(items, 10, isOwnerItem, 100);
}
물론 위처럼 이렇게 만든다면
가능은 하지만 여전히 불편한 상황이 만들어지니
나중에 함수 객체를 배우면
함수 포인터를 인자로 받는 것이 아닌 함수 객체를 인자로 받을 예정
ex. SELECTOR_TYPE* selector, int value 이 부분을 한 번에 객체로 처리할 예정
함수 객체는 사실상 자주 사용할 일은 없지만, 반드시 개념을 확실히 해야 함
간단하게 다시 정리해 보면,
// typedef 정의를 통해 함수 포인터를 만드는 법
typedef int (FUNC_TYPE) (int a, int b);
FUNC_TYPE* fn;
// 함수 포인터를 함수 인자로 받을 때는
typedef bool(SELECTOR_TYPE)(Item* item);
Item* FindItem(Item items[], int itemCount, SELECTOR_TYPE* selector){}
// 하지만 위처럼 두 단계를 거치는 것이 번거롭기 때문에 바로 인자에서 함수 포인터를 만들어서 사용
Item* FindItem(Item items[], int itemCount, bool (*selector)(Item* item)){}
// 함수와 함수 포인터
bool func(Item* item){} // 함수
bool (*funcp)(Item* item) // 함수 포인터
'C++ > [루키스] 콜백함수' 카테고리의 다른 글
[STL 사전지식] 6. 콜백 함수 (0) | 2023.01.25 |
---|---|
[STL 사전지식] 5. 템플릿 기초 (2) (0) | 2023.01.25 |
[STL 사전지식] 4. 템플릿 기초 (1) (0) | 2023.01.25 |
[STL 사전지식] 3. 함수 객체 (0) | 2023.01.23 |
[STL 사전지식] 2. 함수 포인터 (2) (0) | 2023.01.22 |