간단하게 저번 시간에 배운 함수 포인터를 복습해보자
아래와 같이 세 가지의 방식으로 함수 포인터를 만들 수 있었다
1) typedef 없이 만들기
2) typedef 함수 정의로 시작해서 만들기
3) typedef 함수 포인터 정의로 시작해서 만들기(이 부분은 조금 있다가 다룬다)
int Test(int a, int b)
{
return a + b;
}
int main()
{
// 첫번째 방식
int (*fn1) (int, int);
fn1 = &Test;// & 생략가능
// 두번째 방식
typedef int (FUNC_TYPE)(int, int);
FUNC_TYPE* fn2;
fn2 = &Test;
// 간단하고 자주 사용하는 첫번째 방식에 익숙해져야함
}
여기서 잠깐,
typedef에 대해 알아보고 가자
typedef은 커스텀 타입 정의를 의미한다
typedef a b
왼쪽(a)을 오른쪽(b)으로도 정의한다 라는 뜻이다
하지만, 복잡해지면 왼쪽 오른쪽이라는 규칙은 통하지 않는다
이제부터는 typedef를 보면,
선언 문법에서 typedef를 앞에다 붙이는 것으로 이해하자
아래의 코드 예시를 보면, 알 수 있다
// 왼쪽 오른쪽 규칙으로 한다면
// typedef int[20] ARRAY
// 이렇게 만들어져야 하지만
// 위는 잘못된 오류이다
// 앞서말한 선언 문법 앞 붙이기로 이해하면
typedef int ARRAY[20];
typedef int FUNC(int, int); // 중간에 (FUNC) 괄호 생략 가능
// 이렇게 만들 수 있다
다시 본론으로 돌아와 정리해 보면
함수 포인터는 아래와 같이 만들 수 있었다
1) typedef 없이 만들기
2) typedef 함수 정의로 시작해서 만들기
3) typedef 함수 포인터 정의로 시작해서 만들기(이 부분을 지금 다룬다)
그럼 3번의 경우는 어떻게 만들까?
함수 포인터 typedef 정의를 바로 하는 방법은
아래 코드를 통해 알아보자
// 2번으로 만든 것
typedef int FUNC(int, int);
FUNC* fn;
// 3번으로 만든 것
typedef int (*PFUNC) (int, int);
PFUNC pfn;
그런데 함수 포인터를 typedef 정의를 하고 사용하는 경우는 계속 봤지만
함수 typedef 정의를 하고 그것을 포인터가 아닌 로컬로 사용하는 경우가 있을까?
아래를 통해 알아보자
typedef int FUNC(int, int); // 함수 typedef 정의
FUNC t; // 이렇게 사용하는 경우는 없다
왜 위와 같이 사용하는 경우는 없을까?
일반적인 지역변수처럼 스택 메모리에 올라가는 개념이 아니라
함수가 존재하니 타고 가면 구현부가 있을 거다의 의미이다
전방선언처럼 그 어떠한 데이터도, 기능도 차지하지 않음
따라서 함수의 시그니쳐를 만들어서 이것을 로컬로 사용하는 경우는 없다
지금까지
함수 포인터를 만드는 세 가지 방법을 알아보았고
typedef를 어떻게 이해해야 하는지 잠시 알아봤고
그리고 방금 함수 typedef를 그대로 사용하는 일은 없다는 것도 알아보았다
그럼 다시 본론으로 돌아와
아래의 코드를 봐보자
int Test(int a, int b)
{
return a + b;
}
typedef int (*PFUNC) (int, int);
int main()
{
PFUNC pfn = &Test;
Test(1, 2);// 정상적으로 사용 가능하다
}
위와 같이 우리가 배운 함수 포인터를 사용할 수 있는데
아래와 같은 코드에서 발생하는 문제를 주목해 보자
class Knight
{
public:
int GetHp(int, int)
{
return _hp;
}
public:
int _mfn;
int _hp = 100;
};
typedef int (*PFUNC) (int, int);
int main()
{
Knight k1;
PFUNC pfn = &GetHp; // 불가능하다
PFUNC pfn = &Knight::GetHp; // 불가능하다
}
위처럼 멤버 함수 포인터로 이용을 하고 싶지만 불가능하다
방금 만든 *PFUNC와 같은 typedef 함수 포인터는
호출 규약이 동일한 전역 함수, 정적 함수만 담을 수 있다
여기서 정적함수란
예를 들어 클래스 멤버 함수에 static이 붙은 것을 의미한다
위와 같은 문제가 발생하는 이유는
1) 전역 함수, 정적 함수
2) 멤버 함수
1, 2는 서로 함수 호출 규약 자체가 다르니
클래스 멤버 함수는 당연히 안 들어가진다
그럼 지금까지 배운 함수 포인터는 전역 함수와 정적 함수에만 해당하니
클래스 멤버 함수 포인터를 만들어보자
class Knight
{
public:
int GetHp(int, int)
{
return _hp;
}
public:
int _mfn;
int _hp = 100;
};
typedef int(Knight::*PMEMFUNC) (int, int); // 이렇게 만든다
int main()
{
Knight k1;
PMEMFUNC mfn;
mfn = &Knight::GetHp; // 멤버함수는 & 생략 불가능... 그래서 &를 붙이는 걸 습관화
(k1.*mfn)(1, 1); // 이렇게 사용 가능
// 굳이 mfn에 *를 붙인 이유는
// 안 붙이면, 클래스 멤버 함수나 변수로 오해할 수 있으니
// 결국 멤버 함수 포인터인지 구분하기 위함
}
조금 복잡하고 헷갈리긴 하지만
typedef를 할 때 이름 앞에 Knight:: 을 붙여주고
원하는 함수를 메인에서 담을 때에도 &Knight:: 을 붙여주고
사용할 때에도 *를 이름 앞에 붙여준다
그러면 동적할당으로 만들어진 클래스 멤버 함수 포인터는 어떻게 만드는지
아래를 보며 알아보자
class Knight
{
public:
int GetHp(int, int)
{
return _hp;
}
public:
int _mfn;
int _hp = 100;
};
typedef int(Knight::*PMEMFUNC) (int, int); // 이렇게 만든다
int main()
{
Knight* k2 = new Knight();
PMEMFUNC mfn;
mfn = &Knight::GetHp;
((*k2).*mfn)(1, 1); // 만드는 방법 1
(k2->*mfn)(1, 1); // -> 를 이용하는 만드는 방법 2
}
약간 헷갈린다
하지만 어떤 개념인지는 반드시 이해하고 넘어가자
실제 c++ 사용하는 개발자들에게 물어봐도
대부분 모른다고 한다
너무 걱정하지 말고 자주 사용하면서 익숙해지도록 하자
또한, 이렇게 만들어진 멤버 함수 포인터는
결국 Knight 클래스만(해당하는 클래스만) 사용 가능하고
다른 클래스는 불가능하다
하지만, 상속 관계의 경우는 또 달라지지만 그 부분은 너무 복잡하니 나중에 기회가 되면 알아보자
'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 사전지식] 1. 함수 포인터 (1) (0) | 2023.01.22 |