다형성 : 다양한 형태를 가지는 성질, 부모가 자식의 매서드를 사용하는 문법
1. 상속 관계에서만 성립
2. 부모/자식 간에 완전 동일하게 선언된 형태의 함수 필요
3. 오버라이딩 하는 함수 앞에 virtual 키워드 필요
virutal 가상 함수 : virtual 키워드를 사용하여 함수 생성 시 가상 함수 포인터와 가상 함수 테이블이 생성된다.
가상 함수 포인터는 포인터이므로 4Byte의 공간 차지
가상 함수 테이블은 가상 함수 포인터 들의 주소를 저장
런 타임에 동작하는 문법으로, 가지고 있는 테이블에 따라 어떤 함수를 호출할지 결정
=> 동적 바인딩, 재정의 라고 함
가상 함수 포인터와 가상 함수 테이블은 상속 시 자식에게 상속됨
가상 소멸자 : 부모 타입 포인터로 선언된 변수에 자식 타입으로 동적 할당된 객체에 대한 할당 해제?
virtual 사용 시의 문제점 두 가지 : 1. 오버라이딩을 사용하기 위해 부모 클래스에도 같은 형태의 함수를 선언해야 함
2. 부모 클래스에 가상 함수가 너무 많아짐
오버라이딩을 사용하기 위해 부모 클래스에도 같은 형태의 함수를 선언해야 함
해결 -> 순수 가상 함수를 활용한 추상 클래스 생성
순수 가상 함수 : 오버라이딩 목적으로 만들어진 함수에 대해 비어있는 몸체를 선언하는 방식이아닌 0으로 초기화를 진행 하는 방식
ex)
virtual void Func() = 0;
주의점! 1. 해당 함수는 선언부만 존재하는 미완성 함수로, 오버라이딩 목적으로만 사용
2. 부모에게 순수 가상 함수가 존재할 경우, 자식 클래스 전체에 해당 함수를 선언해야 함 해당 주의점은 사용 여부 에 관계가 없다.
추상 클래스 : 1. 순수 가상 함수가 하나라도 존재하는 클래스
2. 추상 클래스는 객체 생성과 선언이 불가능하다
3. 순수 가상 함수는 존재하지 않으며, 해당 이름을 가진 함수의 존재만 알리는 역활이므로 메모리에 할당 할 수 없다
=> 문제 1 해결, But 자식 클래스에 함수를 잔뜩 선언해야함
부모 클래스에 가상 함수가 너무 많아짐
해결 => 오버라이딩 대신 다운 캐스팅을 활용
기존의 캐스팅(C)
ex)
CObj* Obj = new CPlayer;
(CPlayer*)Obj->Render();
강제 형 변환으로써.....
상속관계에 신경 쓰지 않고 서로 정체를 모르는 객체의 타입으로도 변환 가능
-> 따라서 캡슐화, 은닉화 등의 객체 지향 문법이 파괴 된다
-> 캐스팅은 불안정한 문법이다
RTTI(런 타임 타입 정보)
런타임 시 객체의 자료형에 관한 정보를 드러내는 C++ 매커니즘
다운 캐스팅 : 상위 Type에서 하위 Type으로 캐스팅(부모 형 -> 자식 형)
업 캐스팅 : 하위 Type에서 상위 Type으로 캐스팅(자식 형 -> 부모 형)
안전한 캐스팅 : 캐스팅 상태의 존재, 정보에 대해 아는 케이스(자식 -> 부모)
위험한 캐스팅 : 상대의 존재와 정보에 대해 알지 못하는 상태로 캐스팅
안전한 다운 캐스팅 예시
ex)
CObj -> 부모 형
CPlayer -> 자식 형
CObj* m_Obj = new CPalyer;
dynamic_cast<CPlayer*>(m_Obj)->Render();
CObj -> 부모 형
CPlayer -> 자식 형
CObj* m_Obj = new CPlayer;
CPlayer* m_Obj2 = dynamic_cast<CPlayer*>(m_Obj);
m_Obj2->Render();
안전한 업 캐스팅 예시
ex)
CObj -> 부모 형
CPlayer -> 자식 형
CPlayer* m_Player = new CPlayer;
CObj* m_Obj = m_Player; // => 묵시적 형 변환
CObj* m_Obj2 = dynamic_cast<CObj*>(m_CPlayer); // => 명시적 업 캐스팅
위험한 캐스팅 예시
ex)
CObj -> 부모형
CPlayer -> 자식형
CObj* m_Obj = new CObj;
CPlayer* m_Player = dynamic_cast<CPlayer*>(m_Obj);
C++의 캐스팅 문법 네 가지
static_cast : static_cast(m_Obj);
유일하게 컴파일 시점에 동작하는 정적 캐스팅 방식
일반 캐스팅과 달리 상속 관계에서만 작동하는, 논리적인 캐스팅
동작 원리는 일반 캐스팅과 같다.
부모 타입으로 생성된 부모 객체를 자식 타입으로 재생성?
ex)
CObj* m_Obj = new CObj;
CPlayer* m_Player = static_cast<CPlayer*>(m_Obj);
But! static_cast 사용 시 상속 관계임을 확인하므로 동작하긴 함 => 과연 안전한 행위인가?
위험한 캐스팅의 예시와 동일하다 => static_cast는 상속 여부만 파악하며 정적 타이밍에 동작하므로 해당 문제에 대한 검 증을 할 수 없다
dynamic_cast : dynamic_cast<CPlayer*>(m_Obj);
가상 함수가 단 하나라도 존재하는 클래스에 한해서만 사용이 가능 한 문법
=> 부모 클래스의 소멸자는 무조건 virtual화(오버라이딩 참조)되므로 사용에 문제 없음
클래스 포인터 전용 형 변환 방식으로, 원시 자료형에는 사용이 불가능하다
런 타임에 안전한 캐스팅인지 검사를 진행하며 위험한 다운 캐스팅의 경우 NULL값을 반환한다
static_cast에서 검사하지 못하는 부분까지 검사를 진행하여 보다 안전하지만, 연산 속도는 떨어진다
안전성이 확보 된 경우 static_cast 사용 문제점 파악이 목적인 경우 dynamic_cast 사용
const_cast : const int* p = &iNumber;
int* p2 =const_cast<int*>(p);
일시적으로 const를 제거하는 키워드
위 코드와 같은 경우에 p에 대한 값은 변경이 불가능 하지만 아래처럼
const_cast를 진행한 후에는 해당 값의 변경이 가능해진다
포인터 및 래퍼런스에만 사용이 가능하다
reinterpret_cast : int iNumber = 65;
char* ptr = reinterpret_cast<char*>(&iNumber);
cout << ptr << endl;
const를 제외한 모든 포인터 형으로의 형 변환 허용
논리적 / 문법적으로 따지지 않음 -> 이러한 이유로 잘 사용하지 않는 문법