멤버 함수 포인터

Jaemyeong Lee·2024년 12월 16일

게임 서버1

목록 보기
79/220

왜 일반 함수 포인터로는 안 되는가?

함수 종류별 비교

구분예시저장 가능한 포인터 타입
전역 함수int Add(int,int)int (*)(int,int)
정적 멤버 함수static void Foo()void (*)()
비정적 멤버 함수void Player::Attack()void (Player::*)()
  • 비정적 멤버 함수는 호출 시 객체(this)가 필요하므로 일반 함수 포인터 타입과 다릅니다.

핵심 직관

  • 일반 함수 포인터: "어느 함수로 점프할지"만 알면 됨
  • 멤버 함수 포인터: "어느 함수 + 어떤 객체(this)로 호출할지"가 함께 필요

자주 나오는 오류

  • void (*fp)() = &Player::Attack; -> 컴파일 오류
  • 이유: Player::Attackvoid (*)()가 아니라 void (Player::*)() 타입

멤버 함수 포인터 문법

타입 공식

  • 기본형: ReturnType (ClassName::*)(Args...)
  • const 멤버 함수: ReturnType (ClassName::*)(Args...) const

기본 예제

class Player {
public:
    void Attack(int damage) { hp -= damage; }
    int GetHp() const { return hp; }
private:
    int hp = 100;
};

using AttackMemFn = void (Player::*)(int);
using GetHpMemFn = int (Player::*)() const;

AttackMemFn attackFn = &Player::Attack;
GetHpMemFn getHpFn = &Player::GetHp;

Player p;
(p.*attackFn)(30);      // 객체로 호출
int hp = (p.*getHpFn)();

Player* pp = &p;
(pp->*attackFn)(10);    // 포인터로 호출

호출 연산자 정리

  • obj.*memFn : 객체가 있을 때
  • ptr->*memFn : 객체 포인터가 있을 때

오버로드 함수 주의

  • 멤버 함수 오버로드가 있으면 함수명만으로 모호할 수 있습니다.
  • 필요하면 명시적 캐스팅으로 시그니처를 확정해야 합니다.

실전 활용 패턴

입력-행동 매핑

class Player {
public:
    void OnQ() { /* 스킬 Q */ }
    void OnE() { /* 스킬 E */ }
};

using Action = void (Player::*)();
unordered_map<char, Action> keyMap = {
    {'Q', &Player::OnQ},
    {'E', &Player::OnE},
};

// 입력 처리
char key = 'Q';
Player player;
if (auto it = keyMap.find(key); it != keyMap.end()) {
    (player.*(it->second))();
}
  • 키 설정만 바꾸면 행동을 갈아끼울 수 있어 입력 커스터마이징에 유리합니다.

Job 큐와 결합할 때

  • "행동"만 저장하면 멤버 함수 포인터로도 가능
  • 하지만 보통은 "행동 + 대상 + 파라미터"를 함께 저장해야 하므로
    다음 Part의 함수 객체(펑터)가 더 자연스럽습니다.

정적 멤버 함수는 예외

  • static 멤버 함수는 this가 없으므로 일반 함수 포인터에 저장 가능합니다.

한계와 다음 파트 연결

한계설명
문법 복잡성.*, ->*, 타입 선언이 가독성 낮음
시그니처 제약타입이 조금만 달라도 재사용 어려움
데이터 바인딩 불편호출 대상/인자를 함께 묶어 다루기 번거로움
  • 이 한계를 해결하려고 함수 객체(Functor), lambda, std::function을 사용합니다.
  • 다음 Part 4에서 "행동 + 데이터"를 한 객체로 묶는 방식을 학습합니다.

체크 질문 (스스로 답해보기)

  • void (*)()void (Player::*)()는 서로 호환되지 않을까?
  • const 멤버 함수를 가리킬 때 타입 선언이 어떻게 달라질까?
  • obj.*fn, ptr->*fn를 각각 언제 써야 할까?
  • 멤버 함수 포인터만으로 커맨드 큐를 구성할 때 불편한 점은 무엇일까?

profile
李家네_공부방

0개의 댓글