Programming Language/C++

[C++] 상속에서의 형변환 - 업캐스팅(Upcasting)

lumana 2024. 6. 27. 23:50

상속에서의 형변환 - 업캐스팅(Upcasting)

Based *b = new Derived;
  • 우리는 위와 같이 자식 클래스의 객체를 가리켜왔다. 이게 가능한 이유가 업 캐스팅(Upcasting) 때문이다.

  • double a = 1이 가능한 이유는 double a = (double)1; 이라는 묵시적 변환이 이루어졌기 때문이다.

int main() {
    Derived *d = new Derived;
        Base *b = d;
}
  • 위와 같이 자식 클래스에서 부모 클래스로 올라가는 형태의 캐스팅을 업 캐스팅(Upcasting)이라고 한다

  • Upcasting: 자식 클래스 포인터 -> 부모 클래스 포인터(묵시적)

    • 총각은 남자가 되도 문제가 없지만, 남자가 총각인 것은 아니다.

    • 그래서 자식 클래스 포인터(총각)를 부모 클래스 포인터(남자)로 묵시적으로 변환해도 문제가 없다.

  • 업캐스팅이 항상 문제가 없는 것은 아니다. 다음 예시를 확인해보자

#include <iostream>
using namespace std;
class Animal {
   public:
    float xpos = 1; // 초기값 1
    float ypos = 2; // 초기값 2
};

void printAnimals(Animal a[], int n) {
    for (int i = 0; i < n; i++) {
        cout << "(" << a[i].xpos << ", " << a[i].ypos << ")" << endl;
    }
}

int main(void) {
    Animal *a = new Animal [10];  
    printAnimals(a, 10);
        delete[] a;
}
  • 여기까지는 문제가 없다.

  • FlyingAnimal이라는 클래스를 만들었다고 해보자

class FlyingAnimal : public Animal {
   public:
    float zpos = 3;
};

void printAnimals(Animal *a, int n) {
    for (int i = 0; i < n; i++) {
        cout << "(" << a[i].xpos << ", " << a[i].ypos << ")" << endl;
    }
}

int main() {
        FlyingAnimal *a = new FlyingAnimal[10];  
    printAnimals(a, 10);
        delete[] a;
}
  • printAnimals(a, 10)에서 업캐스팅이 일어날 것으로 예상할 수 있다. 실행을 해보자

  • 실행 결과

(1, 2)
(3, 1)
(2, 3)
(1, 2)
(3, 1)
(2, 3)
(1, 2)
(3, 1)
(2, 3)
(1, 2)

  • 컴파일 에러는 발생하지 않았는데 왜 이런일이 발생했을까?

    • 본질적으론 함수를 잘못 사용해서 이런 일이 발생했다.
  • Animal은 x, y를 가지고 있고, FlyingAnimal은 x, y, z를 가지고 있다.

    • Animal은 8바이트 크기를 가지고, FlyingAnimal은 12바이트 크기를 가지게 된다.
    • FlyingAnimal a = new FlyingAnimal[10]에서 12\10 바이트 크기의 배열을 만들었다

      • printAnimal(a, 10);에서 a가 Animal로 묵시적 형변환이 일어난다.

        • Animal은 하나당 8바이트의 크기를 가지고 있다.

        • 그래서 x, y, z, x, y, z....에서 a[0]: (x, y), a[1]: (y, z), a[2]: (z, y), a[3]: (x, y)... 이런 식으로 된다

  • Solution: 포인터 배열을 사용해서 해결해주면 된다

int main(void) {
    Animal **a = new Animal*[10];  // 포인터배열, Flying Animal을 가리키는 포인터들의 배열을 만들자.(배열에는 flying animal의 주소를 가지고 있음.)
}
  • Animal *a = new Animal\[10];

    • Animal의 주소를 담을 수 있는 10칸짜리 배열이다.

      • 배열의 한 칸은, Animal의 주소(Animal*)를 담고 있다.

      • 그리고 이 Animal을 가리키는 또 다른 타입은 Animal*이 된다

void printAnimals(Animal **a, int n) {
    for (int i = 0; i < n; i++) {
        cout << "(" << a[i]->xpos << ", " << a[i]->ypos << ")" << endl;
    }
}

int main(void) {
    Animal **a = new Animal*[10];
    for (int i = 0; i < 10; i++) {
        a[i] = new FlyingAnimal;
    }
    printAnimals(a, 10);
    // 부모 포인터가 자식 포인터를 가리키면 배열에서 문제가 발생함.
    for (int i = 0; i < 10; i++) {
        delete a[i];
    }
    delete[] a;
}
  • for문 내부

    • Animal의 주소를 담을 수 있는 각 배열의 칸에 FlyingAnimal 객체를 가리키도록 해준다

      • 그리고 각 칸마다 FlyingAnimal 객체 하나씩을 생성해줬으므로, 각 칸이 가리키는 메모리를 해제해줘야 한다
  • 이렇게 포인터 배열을 이용하여 수정하면 제대로 작동하게 된다

    • FlyingAnimal 객체 10개를 연속해서 저장한게 아니라, 포인터 배열을 통해서 객체 10개를 따로 생성한 후 가리켰기 때문에, 처음에 말했던 오류가 발생하지 않게 되는 것이다.

    • C++ STL을 사용하면 이런 번잡함을 줄일 수 있다. (추후 다룰 예정)