2020. 11. 10. 19:24ㆍ컴퓨터 수업/C++
@참고
@주의해야 할 것은
정적 바인딩과 정적 타입 바인딩은 다르다.
Static binding is different with Static Type binding.
동적 바인딩과 동적 타입 바인딩은 다르다.
Dynamic binding is different with Dynamic Type binding.
정적 바인딩은 정적 메모리 영역을 사용하는 것이고.
동적 바인딩은 동적 메모리 영역을 사용하는 것이다.
따라서, static으로 선언한 것 이외에는 모두 dynamic이다.
(예외상황이 컴파일러에 따라 발생하지만. 기본적으로는... ex.) a [MAX_ITEMS])
스택 변수와 힙 변수는 모두 dynamic이다.
stack-dynamic binding이 어색하겠지만, 맞는 말이다.
어떤 함수가 호출된다고 할 때, 그 함수가 몇 번 호출될지 컴파일 타임에는 알 수 없기 때문에,
호출될 때마다 그 local variable의 메모리를 할당해야 하기 때문에, 스택 또한 dynamic이다.
@정적 타입 바인딩
오늘 설명할 것은 정적 타입 바인딩인데, 반의어로 동적 타입 바인딩이 있다.
kotlin의 var과 val은 대표적인 동적 타입 바인딩을 이용하는 언어이다.
타입이 고정되어 있지 않으므로, 동적으로 컴파일러가 런타임에 알아서 해결해야 한다.
C++은 대표적인 정적 타입 바인딩을 이용하는 언어이다.
타입이 고정되어 있으므로, 정적으로 런타임에 해결한다.
예를 들어 int x나 double y와 같은 것들이 정적 타입 바인딩이다.
@업 캐스팅의 데이터 손실
int x;
double y; 라 할 때, x = (int) y는 데이터 손실을 수반한다.
따라서, 객체의 업 캐스팅과 다운 캐스팅의 시도에도 데이터 손실이 수반된다.
예를 들어, 파생 클래스를 업 캐스팅하면 아래에 보이는 영역만큼의 데이터가 손실된다.
이를 코드로 확인하자면
Base는 4byte이고 Derived는 5byte인데, Derived를 캐스팅하려 하니 1byte의 데이터 손실이 발생한다는 메시지이다.
derived는 캐스팅되어 더 이상 print에 대한 scope가 존재하지 않는다.
포인터 변수에 대해서도 마찬가지로, 위의 초록색 영역만큼을 잃었으니, print에 대한 스코프가 존재하지 않는 것이 당연하다.
@다운 캐스팅의 데이터 손실
일단 부모 클래스의 다운 캐스팅이 불가 한 이유는 데이터의 관점에서 알 수 있다.
예를 들어 위와 같은 statement는 base를 불안정하게 만든다, Derived 클래스는 char y라는 변수가 동적 바인딩되는데, 위와 같은 statement는 명시적으로 그런 바인딩을 하지 않는다.
다시 말해, 위의 초록색 그림에서 base를 derived로 확장시켜야 하는데, 초록색만큼의 데이터 없이 확장하려 하는 것이다.
즉 4byte를 5byte로 확장하는 것에서 문제가 발생한다.
그런데 포인터 변수로 객체를 가리킬 때는 상황이 조금 다르다.
포인터 변수는 늘 4byte 이므로, 업다운 캐스팅이 늘 자유롭다, 스코프의 문제가 발생하는 것 아니라면.
우선 포인터 변수로 캐스팅을 하면, byte 손실이나 부족은 발생하지 않으므로, 캐스팅이 가능하긴 하다.
하지만, base 포인터 변수가 가리키는 곳의 객체는 다운 캐스팅한다고 해서, 없던 y가 초기화되는 것은 아니므로 그 무엇도 출력되지 않는 문제가 발생한다.
@Virtual과 비 Virtual 메서드로 알아보는 정적 타입 바인딩과 동적 타입 바인딩
<소스코드>
<기본>
<업 캐스팅>
<다운 캐스팅>
다운 캐스팅의 경우로 설명하자면
기본적으로 C++은 정적 타입 바인딩을 이용한다.
derived 변수는 Derived* 타입으로 정적 타입 바인딩되어있으므로, base는 사실 앞에 명시적으로 캐스팅하지 않아도 묵시적 형 변환이 일어난다. (사실 이는 C 컴파일러에서 허용하지 않지만 이론적으론 이러니)
3번째 줄의 derived->print();는 derived 변수의 타입이 정적 타입인 Derived* 이므로 print() 메서드는 당연히 Derived 클래스로 가서 찾는다.
그러나 4번째 줄의 derived->printv();는 경우가 다르다.
virtual - override 키워드로 구현된 메서드는 C++에서 허용하는 동적 타입 바인딩이다.
따라서, 해당 런타임 시점에 derived 포인터 변수가 어떤 메모리를 가리키는지가 중요하다.
따라서, 해당 메모리에 있는 객체는 new Base로 할당된 메모리 이므로, 부모 클래스의 printv를 호출하는 것이다.
@다형성의 실현
single interface(base class의 pointer)로 다양한 entities(derived class 객체) 사용 가능
예를들어, Vector 클래스를 사용한다고 할 때,
Vector<Base *> vec {new Derived, new Derived2, new Derived3};
이라고 정의 할 수도 있다.
여기서 각 index에 -> 오퍼레이터를 사용하여 호출되는 메서드는 정적타입바인딩 되어있으므로
virtual-override 관계가 아닌 메서드는 모두 Base 클래스의 메서드가 호출 될 것이다.
그러나, virtual - override 관계인 메서드는 동적타입바인딩 되어있으므로
각 Derived, Derived2, Derived3 의 메서드가 호출된다.
@추상클래스
순수가상함수 pure-virtual method를 적어도 하나 갖고 있는 클래스를 추상클래스라고 한다.
이는 객체화(instantiation) 될 수 없음.
base class의 pointer로, 다형성에서 single interface로 동작.
(그런, single interface는 업캐스팅을 통해 실현된다.)
자바의 interface가 생각난다.
추상클래스의 순수가상함수는 반드시 override하여 구현해야한다.
따라서, 추상클래스는 파생클래스가 반드시 갖춰야할 것들의 목록을 적는 느낌이기도 하다.
@순수가상함수
순수가상함수의 선언은 다음과 같이 한다.
virtual void draw() = 0;
= 0 이란 statement를 이용해서 순수가상함수를 표현한다.
@내가 구현 단계에서 고려해야 할 것
single interface(base class의 pointer)로 다양한 entities(derived class 객체) 사용 가능
포인터변수로 여러 클래스를 가르켜야 할 때, virtual - override를 사용하지 않으면, 제대로 된 기능을 표현 할 수 없다.
포인터변수라는 single interface로 여러 entitie를 이용한다...
즉 업캐스팅은 자주 있는 일이고, 이때 single interface는 Base class의 pointer로 구성해야한다.
derived class의 pointer라면 다운캐스팅을 해야하므로, 적절치 못하다.
다운캐스팅은 불안정하다.
@vTable
p3를 주의해서 본다면, p3에는 get 메서드가 없다.
'컴퓨터 수업 > C++' 카테고리의 다른 글
템플릿★ (0) | 2020.11.19 |
---|---|
템플릿(임시) (0) | 2020.11.19 |
C++ 상속 (0) | 2020.11.05 |
재귀에 대한 생각. (0) | 2020.11.04 |
Outer, Inner Class의 생성자와 소멸자 호출 타이밍 (0) | 2020.11.04 |