static

Jaemyeong Lee·2024년 12월 6일

게임 서버1

목록 보기
46/220

이 Step에서 다루는 것

  • static의 2가지 의미
    • 공유(클래스 단위): static 멤버 변수/함수
    • 유지(함수 단위): 지역 static 변수(호출 사이 값 유지)
  • static 멤버 변수가 “어디에 하나만 존재”하는지(감각)
  • static 멤버 변수의 정의(ODR) 규칙과 자주 나는 링크 에러 원인
  • 싱글톤(Meyers Singleton)의 형태와 남용/매크로의 위험

학습 목표

  • “객체마다 하나”와 “클래스에 하나”의 차이를 예시로 설명할 수 있다.
  • static 멤버 변수에서 unresolved external symbol이 나는 이유를 설명할 수 있다.
  • 지역 static이 왜 “한 번만 초기화되고 값이 유지”되는지 설명할 수 있다.

핵심 구분: 특정 객체에 종속적인가?

  • 일반 멤버: 객체마다 하나씩 존재 (m1.hp, m2.hp는 서로 다른 저장공간)
  • static 멤버: 클래스 전체에서 하나만 존재 (Marine::attack은 모두가 공유)

static vs 일반 멤버 메모리 감각

┌─────────────────────────────────────────────────────────────────────────────┐
│ Marine m1, m2, m3;  (hp=일반, attack=static)                                  │
├─────────────────────────────────────────────────────────────────────────────┤
│  [일반 멤버 hp]                     [static 멤버 attack]                       │
│  객체마다 따로                      클래스 전체에서 1개                         │
│                                                                               │
│  m1.hp  m2.hp  m3.hp                 Marine::attack (공유)                      │
│                                                                               │
│  static 함수는 객체 없이도 호출 가능: Marine::SetAttack(7)                      │
│  단, static 함수 안에서는 일반 멤버(hp)에 접근 불가(= this가 없음)              │
└─────────────────────────────────────────────────────────────────────────────┘

static 멤버 변수: “클래스에 하나”

class Marine {
public:
    int hp = 40;          // 객체마다 다름
    static int attack;    // 모두가 공유
};

int Marine::attack = 6;   // (중요) 클래스 밖에서 '정의' + 초기화

(실전) 왜 클래스 밖 정의가 필요한가?

  • static int Marine::attack;는 “선언”이고,
  • 실제 저장공간은 딱 한 번 “정의(definition)”되어야 합니다.
  • 이 정의가 빠지면 링크 단계에서 보통 이런 오류가 납니다.
    • unresolved external symbol "public: static int Marine::attack" ...

C++17+: inline static으로 헤더에서 끝내기

헤더만으로 끝내고 싶다면(학습용 감각):

class Marine {
public:
    inline static int attack = 6; // C++17부터 가능(ODR 문제 완화)
};

static 멤버 함수: “this가 없는 함수”

class Marine {
public:
    static void SetAttack(int value)
    {
        attack = value;      // OK (static 멤버)
        // hp = 10;          // ERROR (일반 멤버, this가 없음)
    }

    int hp = 40;
    inline static int attack = 6; // 예시용(C++17)
};

정리:

  • static 멤버 함수는 객체 없이 호출할 수 있습니다: Marine::SetAttack(7);
  • 대신 “특정 객체의 상태(hp)”를 만질 수 없습니다.

활용 예: ID 생성기(전형적인 static 사용처)

class Player {
public:
    Player() : id(s_idGenerator++)
    {
    }

    int id = 0;
    inline static int s_idGenerator = 1; // C++17 예시(아니면 cpp에 정의)
};

포인트:

  • 생성될 때마다 1씩 증가하는 “전역적 상태”가 필요할 때 static이 자연스럽습니다.

지역 변수에 static: “한 번만 초기화 + 호출 사이 값 유지”

#include <iostream>

void CountCalls()
{
    static int counter = 0; // 최초 1회만 초기화
    ++counter;
    std::cout << "함수가 " << counter << "번 호출되었습니다.\n";
}

핵심:

  • 지역 static은 함수가 끝나도 사라지지 않고 값이 유지됩니다.
  • C++11 이후로 “지역 static 초기화”는 멀티스레드 환경에서도 한 번만 일어나도록 보장됩니다.

싱글톤(Singleton): “전역 1개 인스턴스” 패턴

싱글톤은 “딱 하나만 존재하고 어디서든 접근”이 필요할 때 쓰지만,
전역 상태이기 때문에 남용하면 테스트/의존성/순서 문제가 커집니다.

권장 형태(일명 Meyers Singleton)

class UserManager {
public:
    static UserManager& Instance()
    {
        static UserManager instance; // 프로그램 전체에서 1개
        return instance;
    }

    void AddUser() { ++_userCount; }
    int GetUserCount() const { return _userCount; }

    UserManager(const UserManager&) = delete;
    UserManager& operator=(const UserManager&) = delete;

private:
    UserManager() = default;
    int _userCount = 0;
};

사용 예:

auto& mgr = UserManager::Instance();
mgr.AddUser();
mgr.GetUserCount();

매크로로 접근 간소화는 비추천

  • 매크로는 “그냥 텍스트 치환”이라 디버깅/검색/스코프 관리에 불리합니다.
  • 함수(Instance())나 지역 참조(auto& mgr = ...)로 충분히 깔끔하게 쓸 수 있습니다.

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

  • static 멤버 함수에서 일반 멤버에 접근이 안 되는 이유는?
  • static 멤버 변수에서 링크 에러가 나는 가장 흔한 원인은?
  • 지역 static이 “한 번만 초기화”되고 “값이 유지”되는 이유는?
  • 싱글톤을 매크로로 감싸는 방식이 왜 위험할까?

profile
李家네_공부방

0개의 댓글