타입 변환 4: 포인터

 

인프런 Rookiss님의 'Part1: C++ 프로그래밍 입문' 강의를 기반으로 정리한 필기입니다. 
😎[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 강의 들으러 가기!

 

 


 

공통 부분

 

#include <iostream>
using namespace std;

// 타입 변환 (포인터)

class Knight{
public:
    int _hp = 0;
};

class Item{
public:
    Item() { cout << "Item()" << endl; }

    Item(int itemType) : _itemType(itemType) { }

    Item(const Item& item) { cout << "Item(const Item&)" << endl; }

    ~Item() { cout << "~Item()" << endl; }

public:
    int _itemType = 0;
    int _itemDbld = 0;

    char _cummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진 아이템 클래스를 나타내기 위해 표시
};

enum ItemType
{
    IT_WEAPON = 1,
    IT_ARMOR =2,
};

class Weapon : public Item
{
public:
    Weapon() : Item(IT_WEAPON)
    {
        _itemType = 1;
        cout << "Weapon()" << endl;
    }

    ~Weapon()
    {
        cout << "~Weapon()" << endl;
    }

public:
    int _damage = 0;
};

class Armor : public Item{
public:
    Armor() : Item(IT_ARMOR) { cout << "Armor()" << endl; }

    ~Armor() { cout << "~Armor()" << endl; }

public:
    int _defence = 0;
};

 

 


 

연관성이 없는 클래스 사이의 포인터 변환 테스트

 

int main()
{   
    // Stack [ 주소 ] -> Heap [ _hp(4) ]
    Knight* knight = new Knight();

    // 암시적으로는 NO
    // 명시적으로는 OK

    // Stack [ 주소 ]
    Item* item = (Item*)knight;
    item->_itemType = 2; // 4byte로 유효한 정보를 건드리기 때문에 정상적으로 변경된다. 
    item->_itemDbld = 1; // 데이터의 유효 범위를 벋어나서 문제가 된다.

    delete knight;
}

 

delete knight; 하지 않는 경우, 컴파일은 통과되지만  item->_itemDbld = 1;가 문제가 된 상태로 넘어갈 수 있다.

이 경우, 추후에 나비효과가 되어 돌아올 수 있다.


 

 

다운 캐스트: 부모 -> 자식 변환 (Base class -> Derived class)

 

부모 -> 자식 변환은 명시적으로 사용해야 통과된다.

int main()
{
    Item* item = new Item();

    // [ [ Item ]  ]
    // [  _damage  ]
    Weapon* weapon = (Weapon*)item;  // 위험한 상황, 잘못된 코드
    Weapon->_damage = 10;            // 잘못된 메모리를 건드림, 잘못된 코드

    delete item;
}

Object Slicing 을 조심해야 한다.

 

메모리의 크기가 큰 것에서 작은 것으로 변환하는 경우, 엉뚱한 메모리를 건드릴 수 있다. 

위의 경우, item은 weapon보다 메모리의 크기가 크다. (*Weapon)item과 같이 명시적 변화를 하면 엉뚱한 메모리를 건드릴 수 있다.

 

명시적으로 타입 변환할 때는 항상 항상 조심해야 한다!


 

 

자식 -> 부모 변환 테스트

 

int main()
{
    // [ [ Item ]  ]
    // [  _damage  ]
    Weapon* weapon = new Weapon();

    // 암시적으로도 된다!
    Item* item = weapon;    // Item* item = (item*) weapon;  // (item*) 부분이 생략되어있다

    delete weapon;
}

 


 

조심할 사항들

 

명시적으로 타입 변환할 때는 항상 항상 조심해야 한다!
암시적으로 될 때는 안전하다?

 -> 평생 명시적으로 타입 변환(캐스팅)은 안 하면 되는거 아닌가?

int main()
{
    Item* inventory[20] = {};

    srand((unsigned int)time(nullptr));

    for (int i = 0; i < 20; i++)
    {
        int randValue = rand() % 2;  // 0~1
        switch (randValue)
        {
        case 0:
            inventory[i] = new Weapon();
            break;
        case 1:
            inventory[i] = new Armor();
            break;
        }
    }
    
    
    for (int i = 0; i < 20; i++)
    {
        Item* item = inventory[i];
        if (item == nullptr)
            continue;

        if (item->_itemType == IT_WEAPON)
        {
            Weapon* weapon = (Weapon*)item;
            cout << "Weapon Damage : " << weapon->_damage << endl;
        }
    }
}

 

실행화면


 

 

 

 

 

int main()
{
    for (int i = 0; i < 20; i++)
    {
        Item* item = inventory[i];
        if (item == nullptr)
            continue;

        //**  Virtual 함수 변경으로 delete item; 하나로 해결
        if (item->_itemType == IT_WEAPON)
        {
            Weapon* weapon = (Weapon*)item;
            delete weapon;
        }
        else
        {
            Armor* armor = (Armor*)item;
            delete armor;
        }        

        delete item;
    }

    // [결론]
    // - 포인터 vs 일반 타입 : 차이를 이해하자
    // - 포인터 사이의 타입 변환(캐스팅)을 할 때는 매우 매우 조심해야 한다!
    // - 부모-자식 관계에서 부모 클래스의 소멸자에는 까먹지 말고 virtual을 붙이자!!!  // 코딩 테스트와 면접 단골문제!

    return 0;
}

 

 

포인터 vs 일반 타입 : 차이를 이해하자

- 포인터 사이의 타입 변환(캐스팅)을 할 때는 매우 매우 조심해야 한다!
- 부모-자식 관계에서 부모 클래스의 소멸자에는 까먹지 말고 virtual을 붙이자!!!  

 


 

 

전체 코드

 

더보기
#include <iostream>
using namespace std;

// 타입 변환 (포인터)

class Knight
{
public:
    int _hp = 0;
};

class Item
{
public:
    Item()
    {
        cout << "Item()" << endl;
    }

    Item(int itemType) : _itemType(itemType)
    {

    }

    Item(const Item& item)
    {
        cout << "Item(const Item&)" << endl;
    }

    ~Item()
    {
        cout << "~Item()" << endl;
    }

public:
    int _itemType = 0;
    int _itemDbld = 0;

    char _cummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진 아이템 클래스를 나타내기 위해 표시
};

enum ItemType
{
    IT_WEAPON = 1,
    IT_ARMOR =2,
};

class Weapon : public Item
{
public:
    Weapon() : Item(IT_WEAPON)
    {
        _itemType = 1;
        cout << "Weapon()" << endl;
    }

    ~Weapon()
    {
        cout << "~Weapon()" << endl;
    }

public:
    int _damage = 0;
};

class Armor : public Item
{
public:
    Armor() : Item(IT_ARMOR)
    {
        cout << "Armor()" << endl;
    }

    ~Armor()
    {
        cout << "~Armor()" << endl;
    }

public:
    int _defence = 0;
};


int main()
{
    // 연관성이 없는 클래스 사이의 포인터 변환 테스트
    {
        // Stack [ 주소 ] -> Heap [ _hp(4) ]
        Knight* knight = new Knight();

        // 암시적으로는 NO
        // 명시적으로는 OK

        // Stack [ 주소 ]
        // tem* item = (Item*)knight;
        // item->_itemType = 2;
        // item->_itemDbld = 1;

        delete knight;
    }

    // 부모 -> 자식 변환 테스트
    {
        Item* item = new Item();

        // [ [ Item ]  ]
        // [  _damage  ]
        Weapon* weapon = (Weapon*)item;  // 위험한 상황, 잘못된 코드
        Weapon->_damage = 10;            // 잘못된 메모리를 건드림, 잘못된 코드

        delete item;
    }


    // 자식 -> 부모 변환 테스트
    {
        // [ [ Item ]  ]
        // [  _damage  ]
        Weapon* weapon = new Weapon();

        // 암시적으로도 된다!
        Item* item = weapon;    // Item* item = (item*) weapon;  // (item*) 부분이 생략되어있다

        delete weapon;
    }


    // 명시적으로 타입 변환할 때는 항상 항상 조심해야 한다!
    // 암시적으로 될 때는 안전하다?
    // -> 평생 명시적으로 타입 변환(캐스팅)은 안 하면 되는거 아닌가?

    Item* inventory[20] = {};

    srand((unsigned int)time(nullptr));

    for (int i = 0; i < 20; i++)
    {
        int randValue = rand() % 2;  // 0~1
        switch (randValue)
        {
        case 0:
            inventory[i] = new Weapon();
            break;
        case 1:
            inventory[i] = new Armor();
            break;
        }
    }

    for (int i = 0; i < 20; i++)
    {
        Item* item = inventory[i];
        if (item == nullptr)
            continue;

        if (item->_itemType == IT_WEAPON)
        {
            Weapon* weapon = (Weapon*)item;
            cout << "Weapon Damage : " << weapon->_damage << endl;
        }
    }



    return 0;
}