나만의 작은 도서관
[C++][Callback] #2. 함수 포인터(Function Pointer) 본문
함수 포인터란?
리턴_타입 (*포인터_이름)(매개변수_타입)
// example
int (*ptr_fn)(int, int);
int (*ptr_fn)(int (*)[3], int);
- 함수 포인터란 callable 한 개체 중 하나로, 함수의 위치를 가리키는 포인터를 의미한다.
- 함수를 변수처럼 포인터로 가리킬 수 있는 이유는 함수가 프로세스 메모리 구조 중 코드 영역(code segment)에 저장되어 있기 때문이다.
- 함수 포인터는 C언어에서 넘어온 문법이다.
- 함수는 정적 배열처럼 이름이 시작 주소값이 되므로, 함수 포인터에는 함수 이름을 대입한다.
함수 포인터 예제
int max(int a, int b); // 함수 선언
int (*ptr_max)(int, int); // 함수 포인터 선언
ptr_max = max; // 함수 저장
ptr_max(1, 2); // = max(1, 2);
typedef, using 키워드 사용하는 법
// typedef 리턴_타입(재정의_타입_이름)(매개변수_타입)
typedef int(FUNC_TYPE)(int, int);
// using 재정의_타입_이름 = 리턴_타입(매개변수_타입)
using FUNC_TYPE2 = int(int, int);
// 또는
// typedef 리턴_타입(*재정의_포인터_타입_이름)(매개변수_타입)
typedef int(*FUNC_TYPE_POINTER)(int, int);
// using 재정의_포인터_타입_이름 = 리턴_타입(*)(매개변수_타입)
using FUNC_TYPE_POINTER2 = int(*)(int, int);
- 함수는 리턴 타입과 매개변수 타입의 조합이 하나의 타입이 된다.
- 예를 들어, int (*)(int, int)는 두 개의 int 타입 매개변수와 int 타입의 리턴값을 가지는 함수 타입을 의미한다.
- typedef 키워드의 경우 재정의된 타입의 이름은 포인터 이름 자리에 위치한다.(굉장히 헷갈리는 위치다)
typedef, using 키워드 사용 예제
- 아래 예시에서 포인터를 뺀 FUNC_TYPE 방식으로 재정의하는 경우, 선언 시 *를 실수로 누락하면 포인터가 아닌 전방 선언을 하게 되므로 주의해야 한다.
// FUNC_TYPE myFunc; => int myFunc(int, int)와 일치(전방 선언)
FUNC_TYPE* myFunc = Add;
FUNC_TYPE2* myFunc2 = Add;
FUNC_TYPE_POINTER myFuncPointer = Add;
FUNC_TYPE_POINTER2 myFuncPointer2 = Add;
// 함수 포인터는 *(접근 연산자)로 적어도 함수 주소에 접근한다.
cout << (*myFunc)(1, 2) << '\\n';
cout << myFunc(1, 2) << '\\n'; // 위 문장과 완벽히 일치
cout << myFunc2(3, 4) << '\\n';
cout << myFuncPointer(5, 6) << '\\n';
cout << myFuncPointer2(7, 8) << '\\n';
실행 결과
3
3
7
11
15
멤버 함수 담기
// typedef 리턴_타입(클래스_이름::*재정의_타입_이름)(매개변수_타입)
typedef int(CLASS_NAME::*MEMBER_FUNC_TYPE_POINTER)(int, int);
// using 재정의_포인터_타입_이름 = 리턴_타입(클래스_이름::*)(매개변수_타입)
using MEMBER_FUNC_TYPE_POINTER2 = int(CLASS_NAME::*)(int, int);
- 멤버 함수는 해당 함수가 속한 객체를 넘겨주는 것부터 시작하기 때문에 호출 규약을 맞추기 위해선 멤버 함수가 소속된 클래스 이름을 위와 같이 추가로 적어줘야 한다.
멤버 함수 담기 예제
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Knight{
public:
void GetHp() {cout << "Hp: " << _hp << '\\n';}
private:
int _hp = 100;
};
typedef void(Knight::*MEMBER_FUNC_TYPE_POINTER)();
using MEMBER_FUNC_TYPE_POINTER2 = void(Knight::*)();
int main(){
Knight k1;
// 멤버 함수는 암시적 변환을 하지 않기 때문에 &를 붙여준다.
MEMBER_FUNC_TYPE_POINTER func_ptr = &Knight::GetHp;
MEMBER_FUNC_TYPE_POINTER2 func_ptr2 = &Knight::GetHp;
(k1.*func_ptr)(); // k1.GetHp()
(k1.*func_ptr2)(); // k1.GetHp()
return 0;
}
실행 결과
Hp: 100
Hp: 100
함수 포인터의 단점
함수 포인터는 문법이 난해하다는 점을 제외하고도 1) 함수 타입이 일치하지 않는 함수는 사용할 수 없다는 점, 2) 상태를 가질 수 없어 클로져 구현을 할 수 없다는 점 등 여러 단점들이 있다.
따라서, C++로 넘어오면서 함수 포인터보다 편리하고 강력한 기능인 "함수 객체(functor)"를 주로 사용한다.
참고 자료
'C++ > 문법 및 메소드(STL)' 카테고리의 다른 글
[C++][Callback] #4. 람다 표현식(Lambda Expression) (0) | 2025.04.03 |
---|---|
[C++][Callback] #3. 함수 객체(functor) (0) | 2025.04.03 |
[C++][Callback] #1. 콜백 함수와 Callable, 그리고 std::function (0) | 2025.04.02 |
[C++] 전방 선언(Forward Declaration) (0) | 2025.03.28 |
[C++] 캐스팅(Casting) (0) | 2025.03.28 |