본문 바로가기
C++/기초(두들낙서)

[C++] 상속에서의 형변환 (💢static_cast다시보기)

by Meaning_ 2022. 7. 12.
728x90
반응형

업캐스팅

 

- 자식클래스에서 부모클래스로 올라가는 형변환

- 자식클래스 포인터 -> 부모 클래스 포인터

- 묵시적 형변환이 일어난다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using namespace std;
 
 
class Animal {
 
public:
    float xPos=1;
    float yPos=2;
    
};
 
 
 
class FlyingAnimal :public Animal {
 
public:
 
    float zPos=3;
 
};
void printAnimals(Animal* a, int n) {
    //a[]와 *a는 같다. 배열포인터
    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;
 
 
}
cs

 

여기서 두가지를 중요하게 볼 수 있는데

1. FlyingAnimal 타입의 배열 a가 printAnimals로 가면서 Animal타입으로 업캐스팅

2. 객체배열을 동적할당하고 싶을 때

class이름 *변수명 = new class이름[배열의 크기]

 

만약 그냥 객체 배열 할당했다면 Animal aa[10]  이런식으로 가능하다. 

 

객체배열과 객체 포인터 배열을 헷갈리지 않도록 하자. 

 

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=hgt2768&logNo=220686089698 

 

C++ 객체와 객체 배열의 동적 생성 및 반환

앞에서는 기본 형식과 배열의 동적 할당에 대해 배웠다 그렇다면 이번에는 객체와 객체배열의 동적할당에 ...

blog.naver.com

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=go4693&logNo=221114088412 

 

C++ 클래스 배열(객체 배열, 객체 포인터 배열) / this 포인터

객체 배열, 객체 포인터 배열은 구조체 배열의 선언 이유와 유사하다. 객체 배열 객체 배열 선언 시 생성자...

blog.naver.com

 

이제 지금부터 업캐스팅의 문제점을 알아볼것이다.

 

우리는 출력값이 (1,2,3) 이 10번출력될 것 같았지만 실상은 그렇지 않았다.

 

FlyingAnimal은 멤버변수가 상속을 받아오면서 3개이다. x,y,z

 

 

근데 이게 Animal 배열로 업캐스팅이 되는데

Animal 배열은 변수가 2개뿐이다.(x,y)

 

 

이런식으로 출력이 되어버린다..!

 

이런문제를 해결하기 위해서는 객체배열 동적할당이 아닌 포인터배열을 사용해주면 된다!

 

포인터 배열을 동적할당하기 위해서 더블포인터를 사용했다. 

 

포인터 배열을 선언해서 10칸짜리 1차원 포인터 배열을 만들어준다.

그리고 그 한칸 한칸을 다 FlyingAnimal을 가리키게 한다. 

이때 동적할당을 해주면서 배열 한칸이 x,y,z를 다 가리키게 된다.

 

그러고 다시 한칸한칸을 delete해주고 

마지막에 포인터배열 하나를 메모리해제 해주면 된다.

그리고 printAnimals의 매개변수도 포인터배열을 받아줘야하기 때문에 더블포인터를 쓴다.

해결이 되었다!

 

 

다운 캐스팅

부모에서 자식으로 형변환 되는 것을 말한다.

 

이런경우는 b에서는  Drv1을 가리키고 있어도 a밖에 접근을 못한다. 

그러면 b를 통해 Drv1의 고유의 멤버들에 접근하려면 어떻게 해야할까?

그러면 b를 Drv1을 가리키는 포인터로 다운캐스팅을 해서 d1이 Drv1을 접근할 수 있게끔 할 수 있다. 

 

근데 무지성 형변환이 되는 경우도 있을 수 있다. 이런것을 막아주는게 static_cast이다.

결국 이 말은  부모클래스 가리키는 포인터 b가 사실상은 Drv1을 가리키고 있으니까 b를 Drv1으로 형변환해서 d1에 넣어주는 것이다. 

즉, b의 주소값은 가지고 오는데 Drv1을 가리키게 하라는 것이다.

 

문제는 여기서 생겨버린다. b의 주소값을 가져오는데 Drv2를 가리키게 해야한다.

근데 문제는 Base를 가리키는 포인터 b는 사실상 Drv1을 가리키니까

사실상 d2는 Drv1을 가리키면서(여기에 b의 주소가 들어간다) Drv2를 가리킨다고 착각하게 된다..!

 

RTTI와 dynamic cast

https://www.youtube.com/watch?v=miIHJGasPsc&list=PLlJhQXcLQBJqywc5dweQ75GBRubzPxhAk&index=89 

dynamic cast는 동적 형변환이다 (런타임)

++) static cast는 정적 형변환(컴파일)

 
컴파일 타임에 포인터가 어떤걸 가리키는지 모를 때는 런타임에 타입에 대한 정보를 알아내야 한다.
C++에서 RTTI가 필요한 경우는 가상함수를 쓸 때이다!

아무리 캐스팅을 시켜줘도 함수 f를 호출하면 결국 Base에 있는 함수 f가 호출된다.

정적바인딩 때문이다. 

 

런타임에 b가 어떤걸 가리키는지 알아야 동적바인딩을 할 수 있을 것이다.

그러면 이전에 공부했던 대로 Base의 f함수에 virtual을 쓰면 될 것이다.

 

만약  Base를 Drv로 dynamic_cast 해주고 싶으면 Base클래스가 가상함수가 하나가 포함되어야 한다.

그래서 dynamic_cast를 하려면 RTTI를 지원해야하고, RTTI를 하려면 가상함수가 있어야 한다. 

 

dynamic_cast는 형변환에 성공했을때만 실행한다. 그니까 캐스팅하려는 클래스와

캐스팅하려는 객체의 실제 클래스가 같아야 실행된다.

 

Rectangle* r=static_cast<Rectangle*>(shapes[i]) 일 때

shapes[i]가 실제로 Rectangle을 가리켜야 dynamic_cast가 일어난다.

 

 

++) 정리

 

static_cast는 가리키는 타입과 형변환 시켜주려는 타입이 같다는 것이 명확이 보일때

예를 들어 A가 부모클래스이고 B가 자식클래스 일때 

A *a=new B;  이라면 a를 b로 형변환 시키려하고, a가 사실상 b를 가리키고 있다면  static_cast<B*>(a) 를 쓰면된다.

 

dynamic_cast는 명확하지 않을 때 쓰면 되는데 단, 성능이 느려진다.

만약 가리키는 타입과 형변환 시켜주려는 타입이 같으면 dynamic_cast가 될 것이고

다르다면 NULL이 반환될 것이다. 

728x90
반응형

댓글