전체 코드

📌 초기화란?

초기화는 클래스의 멤버 변수를 선언과 동시에 적절한 값으로 설정하는 과정을 의미합니다.
C++에서는 다양한 초기화 방법이 존재하며, 초기화를 적절히 수행하지 않으면 쓰레기 값(Garbage Value)이 들어갈 수 있습니다.


✅ 초기화를 하는 이유

  1. 버그 예방

    • 변수를 초기화하지 않으면 예측할 수 없는 쓰레기 값이 들어가 예기치 않은 버그가 발생할 수 있습니다.
    • 특히 포인터 등의 메모리 주소값이 연루된 경우 초기화가 필수적입니다.
  2. 메모리 안정성 확보

    • C++에서는 변수를 선언만 하면 값이 설정되지 않은 상태입니다.
    • 사용하기 전에 명확한 값을 설정하는 것이 중요합니다.
  3. 코드의 유지보수성과 가독성 향상

    • 명확하게 초기화하면 코드의 가독성이 증가하고, 디버깅이 쉬워집니다.

✅ 초기화 방법

초기화는 여러 방식으로 수행할 수 있습니다.

  1. 생성자 내부에서 초기화

    class Knight {
    public:
        Knight() { _hp = 100; } // 생성자 내부에서 초기화
    public:
        int _hp;
    };
    • 생성자 내부에서 값을 설정하는 방법입니다.
    • 단점: 멤버 변수가 먼저 생성되고 나서 값을 대입하기 때문에, 초기값이 불필요하게 두 번 설정될 수 있습니다.
  2. 초기화 리스트 (Initializer List)

    class Knight {
    public:
        Knight() : _hp(100) {} // 초기화 리스트를 이용한 초기화
    public:
        int _hp;
    };
    • 멤버 변수가 객체가 생성되는 동시에 초기화됩니다.
    • 생성자 내부에서 초기화하는 방식보다 성능상 이점이 있습니다.
    • 특정 변수(참조 타입, const 타입 등)는 반드시 초기화 리스트를 사용해야 합니다.
  3. C++11 문법을 사용한 초기화

    class Knight {
    public:
        int _hp = 100; // C++11 이후 문법
    };
    • 멤버 변수를 선언하면서 초기화하는 방법입니다.
    • 가장 간편한 방식이며, 코드의 가독성이 뛰어납니다.
    • 하지만 복잡한 객체 초기화나 상속 관계에서는 사용이 제한될 수 있습니다.

✅ 초기화 리스트가 필요한 경우

초기화 리스트는 다음과 같은 상황에서 필수적으로 사용됩니다.

  1. 상속 관계에서 부모 클래스의 특정 생성자를 호출해야 할 때

    class Player {
    public:
        Player(int id) { cout << "Player(int id)" << endl; }
    };
    
    class Knight : public Player {
    public:
        Knight() : Player(1) {} // 부모 클래스 생성자를 호출
    };
    • Player(1)을 호출하려면 반드시 초기화 리스트를 사용해야 합니다.
  2. 참조(Reference) 멤버 변수 초기화

    class Knight {
    public:
        Knight(int& hp) : _hpRef(hp) {} // 초기화 리스트 필요
    public:
        int& _hpRef;  // 참조는 선언과 동시에 초기화되어야 함
    };
    • 참조 변수는 생성자 내부에서 초기화할 수 없습니다.
    • 반드시 초기화 리스트를 사용하여 초기화해야 합니다.
  3. const 멤버 변수 초기화

    class Knight {
    public:
        Knight() : _hpConst(100) {} // 초기화 리스트 필요
    public:
        const int _hpConst;
    };
    • const 변수는 초기화 이후 변경이 불가능하기 때문에 초기화 리스트로 설정해야 합니다.
  4. 클래스 멤버가 또 다른 객체를 포함할 때

    class Inventory {
    public:
        Inventory(int size) { cout << "Inventory(int size)" << endl; }
    };
    
    class Knight {
    public:
        Knight() : _inventory(20) {} // 초기화 리스트 사용
    private:
        Inventory _inventory;
    };
    • _inventory(20)을 통해 생성자에서 직접 초기화할 수 있습니다.
    • 생성자 내부에서 대입하는 방식은 불필요한 임시 객체가 생성될 수 있어 비효율적입니다.

📌 초기화 리스트 예제 코드

#include <iostream>
using namespace std;

class Inventory {
public:
    Inventory() { cout << "Inventory()" << endl; }
    Inventory(int size) { cout << "Inventory(int size)" << endl; _size = size; }
    ~Inventory() { cout << "~Inventory()" << endl; }

public:
    int _size = 10;
};

class Player {
public:
    Player() {}
    Player(int id) { cout << "Player(int id)" << endl; }
};

// Knight는 Player를 상속받음 (Is-A 관계)
class Knight : public Player {
public:
    // 초기화 리스트 사용
    Knight() : Player(1), _hp(100), _inventory(20), _hpRef(_hp), _hpConst(100) {
        cout << "Knight()" << endl;
    }

public:
    int _hp;               // 초기화되지 않으면 쓰레기 값이 들어감
    Inventory _inventory;  // 포함 관계 (Has-A)

    int& _hpRef;          // 참조 타입 (초기화 리스트 필요)
    const int _hpConst;   // const 변수 (초기화 리스트 필요)
};

int main() {
    Knight k;
    cout << "Knight의 HP: " << k._hp << endl;

    if (k._hp < 0) {
        cout << "Knight is Dead" << endl;
    }

    return 0;
}

✅ 실행 결과

Player(int id)
Inventory(int size)
Knight()
Knight의 HP: 100
~Inventory()

🔍 분석

  1. Knight k; 객체가 생성되면서
    • Player(1) 호출
    • Inventory(20) 호출
    • Knight() 생성자 실행
  2. 프로그램 종료 시 ~Inventory()가 호출됨.

profile
李家네_공부방

0개의 댓글