상속에서의 형변환 - 업캐스팅(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을 사용하면 이런 번잡함을 줄일 수 있다. (추후 다룰 예정)
'Programming Language > C++' 카테고리의 다른 글
[C++] 상속에서의 형변환 - RTTI와 dynamic_cast (0) | 2024.06.28 |
---|---|
[C++] 상속에서의 형변환 - 다운캐스팅 (0) | 2024.06.27 |
[C++] 생성/소멸자 실행 순서와 가상 소멸자 (0) | 2024.06.27 |
[C++] 순수 가상 함수와 추상 클래스 (0) | 2024.06.27 |
[C++] 상속이 필요한 이유(2) (0) | 2024.06.27 |