아이템 드랍 실습

Jaemyeong Lee·2024년 8월 30일

게임 서버1

목록 보기
52/220

이 Step에서 다루는 것

  • 상속 구조로 “공통 데이터 + 타입별 데이터”를 분리하는 방법
  • 다형성 포인터(Item*)로 “Weapon/Armor를 한 타입으로 다루는” 이유
  • (필수 규칙) 부모 소멸자는 virtual
  • Drop 함수 설계의 핵심: 소유권(누가 delete?)을 코드로 명확히

학습 목표

  • Item* 하나로 무기/방어구를 다룰 수 있는 이유를 설명할 수 있다.
  • 부모 포인터로 delete 하는 구조에서 virtual 소멸자가 필수인 이유를 설명할 수 있다.
  • Drop 함수가 “포인터를 반환”할 때, 소유권 규칙을 어떻게 정해야 하는지 설명할 수 있다.

상속 구조 설계(공통 vs 타입별)

  • Item(부모): 모든 아이템이 공통으로 갖는 정보(아이디/개수/희귀도/타입 등)
  • Weapon/Armor(자식): 해당 타입만 필요한 정보(공격력/방어력 등)

아이템 상속 구조(ASCII)

        ┌──────────┐
        │  Item    │  _itemId, _itemCount, _rarity, _itemType
        └────┬─────┘
             │
      ┌──────┴──────┐
      │             │
┌─────▼─────┐  ┌────▼─────┐
│  Weapon   │  │  Armor   │
│  _damage  │  │ _defence │
└───────────┘  └──────────┘

(필수) virtual 소멸자: 부모 포인터 delete를 안전하게

Item을 Item*로 들고 있다가 나중에 삭제하는 구조라면,
부모 소멸자는 반드시 virtual이어야 합니다.

class Item {
public:
    virtual ~Item() = default; // 필수 규칙
};

이 규칙이 없으면:

  • Item* p = new Weapon(); delete p; 같은 코드에서
  • 자식 소멸자가 호출되지 않을 수 있어(UB/자원 미정리) 사고가 납니다.

DropItem 로직의 핵심: “소유권”을 정하자

방법 A) 학습용(로우 포인터): “호출자가 delete 책임”

#include <cstdlib>

Item* DropItemRaw()
{
    if (std::rand() % 2 == 0)
        return new Weapon();
    else
        return new Armor();
}

// 사용 예(핵심: 반드시 delete)
Item* item = DropItemRaw();
// ... 사용 ...
delete item;
item = nullptr;

이 방식은 배우기엔 좋지만, 실전에서는 “delete 누락”이 너무 쉽게 발생합니다.

방법 B) 권장(RAII): std::unique_ptr<Item>로 소유권을 타입에 담기

#include <cstdlib>
#include <memory>

std::unique_ptr<Item> DropItem()
{
    if (std::rand() % 2 == 0)
        return std::make_unique<Weapon>();
    else
        return std::make_unique<Armor>();
}
// unique_ptr은 범위를 벗어나면 자동으로 delete(누수 방지)

이후 Step에서 스마트 포인터를 배우면, Drop 함수는 거의 항상 이런 형태로 귀결됩니다.


아이템 타입/희귀도: 매직 넘버를 enum으로 고정

enum ItemRarity { IR_None, IR_Normal, IR_Rare, IR_Unique };
enum ItemType { IT_None, IT_Weapon, IT_Armor };

class Item {
public:
    explicit Item(ItemType type) : _itemType(type) {}
    virtual ~Item() = default;
    virtual void PrintInfo() {}

    ItemType GetItemType() const { return _itemType; }

protected:
    int _itemId = 0;
    int _itemCount = 0;
    ItemRarity _rarity = IR_None;
    ItemType _itemType = IT_None;
};

포인트:

  • “그냥 int”로 타입/희귀도를 들고 있으면, 실수로 엉뚱한 값이 들어가도 알아차리기 어렵습니다.
  • enum으로 의미를 고정하면 출력/디버깅/분기 코드가 훨씬 읽기 쉬워집니다.

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

  • Item* 하나로 Weapon/Armor를 담아둘 수 있는 이유는?
  • 부모 포인터로 delete하는 구조에서 왜 virtual ~Item()이 필수일까?
  • Drop 함수가 포인터를 반환할 때 “누가 delete?”를 어떻게 설계해야 안전할까?

profile
李家네_공부방

0개의 댓글