class A {};
class B : A {};
class C : B {};
A* c = new C();

이러한 구조에서 C객체 안에는 A와 B라는 subobject를 가짐
반대로, C는 complete object임
complete object는 메모리상으로 클래스 전체(C)가 객체로 존재함을 의미
다른 객체의 일부(subobject)가 아닌, 독립적으로 존재하는 객체
이 객체의 상속받은 각 부분들은 이 객체 안에서의 subobject라 부름
가상함수를 최소 하나 이상 가지는 객체를 다형성 객체라고 함
가상함수를 이용하여 런타임 중에 다양한 파생클래스의 함수를 호출가능하므로
가상함수가 없는 객체는 비다형성 객체
dynamic_cast < type-id > ( expression )
type_id는 클래스 포인터 또는 클래스 참조자이거나 void*여야만 함
왜냐하면 void*는 범용적으로 사용하기 위해서.
class A {virtual void f() {}}; // 다형클래스
class B {virtual void f() {}}; // 다형클래스
A* pa = new A;
B* pb = new B;
void* pv = dynamic_cast<void*>(pa); // pv는 A의 객체를 가리킴
pv = dynamic_cast<void*>(pb); // 이제 pv는 B의 객체를 가리킴
클래스 포인터/참조자는 애초에 dynamic_cast가 상속구조에서 서로 다른 계층으로의 변환을 위해 사용되기 때문
반환값은 type_id이며, 포인터로 변환 실패시 nullptr을 반환, 참조자로 변환 실패시 bad_cast exception을 반환
class Animal {virtual void speak() { /* */}};
class Dog : public Animal {void speak() override {/* */}};
Animal* a = new Dog;
Dog* dog = dynamic_cast<Dog*>(a); // 다형성 객체여서 가능
주로 부모클래스 포인터에서 자식클래스 포인터로 바꾸는 다운캐스팅에 사용됨
다운캐스팅을 안전하게 하려면, 객체 포인터의 런타임 중 실제 객체 타입을 알아야 함
RTTI(run-time type information)를 통해, 현재 포인터가 complete object를 가리키는건지 subobject를 가리키는지 알 수 있음
그래서 이 포인터를 안전하게 다른 클래스타입으로 변환할 수 있는지 확인할 수 있다(상속구조이면 안전)
이렇게 런타임 중에, 계층구조를 확인하여 안전하게 변환이 이루어지는지 확인하고 변환해주므로 dynamic_cast는 다운캐스팅에 자주 사용된다
RTTI를 사용하므로, 다형성 객체에만 사용 가능.
다형성객체에서 가상함수때문에 vtable이 생성되고, vtable과 관련하여 RTTI도 생성되기 때문.
비다형성 객체는 vtable이 없으므로 RTTI도 없음. 따라서 비다형성에 사용하면 런타임 에러 발생
근데 업캐스팅은 RTTI없어도 되므로 비다형성에 사용가능하긴하지만 보통 static_cast를 사용
유지보수와 안정성
다운캐스트는 실제 객체 타입을 직접 알아야만 쓸 수 있다
객체지향 원칙에 맞지 않음
OOP는 다형성을 이용하여, 부모클래스 포인터만으로 어떤 자식클래스든 동일한 인터페이스(가상 함수)를 통해 동작하도록 함
안전 문제
base 포인터가 derived가 아니라, base인 complete object를 가리키고 있으면, 잘못된 다운캐스트로 런타임 에러 발생
성능 문제
dynamic_cast는 런타임에 타입 체크를 하므로, 가상함수 호출보다 느림
대부분의 경우, 가상 함수 호출로 이미 원하는 동작을 구현 가능
그럼에도 불구하고 다운캐스팅 해야한다면, dynamic_cast통해 안전하게 사용
가상함수의 오버헤드 때문에 직접 다운캐스팅하여 파생클래스의 함수를 호춯하는 경우가 있다. 이때는 당연히 static_cast를 이용하여 성능을 끌어올림
dynamic_cast < type-id > ( expression )
type_id로는 훨씬 더 폭넓게 사용 가능
캐스팅 잘 되면, 변환해서 반환해주고, 변환에 문제 있어 실패하면 아예 컴파일 에러 발생
class Animal {};
class Dog : public Animal {};
Dog* d = new Dog;
Animal* a = static_cast<Animal*>(d); // 업캐스팅
다운캐스팅에는 RTTI가 필요하므로 런타임에 검사해주는 dynamic_cast로 안전하게 다운캐스팅함
근데 업캐스팅은 RTTI 상관 없이, 컴파일 타임에 상속구조 보고 안전한지가 판단됨
따라서 static_cast를 이용하여 컴파일 타임에 변환이 안전한지 판단하고, 문제없으면 컴파일 후 런타임에 변환해줌
그래서 RTTI가 필요한 다운캐스팅에는 사용 안 하고, 캐스팅이 안전한 업캐스팅에 사용
만약 실제 런타임 타입이 다를 경우에는 에러가 아니라 캐스팅해주어 반환해줌.
개발자가 잘 감당해야함
class Animal { };
class Dog : public Animal { public: int k = 5; };
Animal* a = new Animal; // Animal 객체만 할당 (k 없음)
Dog* d = static_cast<Dog*>(a); // 잘못된 다운캐스팅. 반환은 해줌
d->k; // 원래 사용하지 않았던 메모리에 접근. 쓰레기값 나오거나, 에러 발생 가능
int->char 변환시 해주지만, 오버플로우 발생 가능사실 비다형성 객체라 해도, 실제 타입은 런타임에 정해짐 A* a = new B();
근데 왜 비다형성 객체는 RTTI를 가지지 않는지가 궁금하였음
비다형성 객체는 가상함수가 없기에, 부모 포인터로 자식 객체를 가리켜도 멤버 함수 호출, 멤버 변수 접근 등은 항상 부모 타입 기준으로만 동작함
다운캐스팅도 의미가 없는게, 어차피 가상함수도 없으니까 처음부터 자식클래스 포인터로 만들면 됨
그래서 RTTI가 필요 없어 사용 안 함
const_cast <type-id> (expression)
type_id : 이전 캐스팅들과 다르게 거의 대부분의 자료형으로 변환 가능
변수, 포인터, 참조, 함수에 붙은 const(상수성) 또는 volatile(변동성) 속성을 일시적으로 없애거나 붙이고 싶을 때 사용
언제든 값이 수없이 바뀔 수 있다고 지정하는 키워드
값이 엄청 바뀌므로 컴파일러에게 최적화, 캐싱 등을 하지 말라고 함
특히 프로그램 외부에서도 값을 바꿀 수도 있음
그래서 최적화, 캐시하지 말고 메모리에서만 변수를 읽고 값 수정하도록 하여 안정성을 부여
const제거하는 예시 코드const int a = 10;
int* p = const_cast<int*>(&a); // const 제거. 갑 수정 가능
int num = 42;
const int &ref = num; // ref로 값 수정하면 에러
int &modRef = const_cast<int &>(ref);
const 추가하는 코드지만, 사실 이렇게 안 씀int b = 30;
const int* pcb = const_cast<const int*>(&b); // const 추가
this는 const MyClass*를 의미.const_cast로 MyClass*인 non const로 바꾸면서, 내부 값을 수정할 수 있게 됨class MyClass {
public:
void f() const {
const_cast<MyClass*>(this)->x = 5;
}
private:
int x = 0;
};
volatile관련 코드volatile int flag = 0;
int* pFlag = const_cast<int*>(&flag); // volatile 제거
int normal = 5;
volatile int* vp = const_cast<volatile int*>(&normal); // volatile 추가
reinterpret_cast < type-id > ( expression )
모든 포인터를 다른 포인터 타입으로 변환할 수 있다
또한 모든 정수 타입을 모든 포인터 타입으로 변환할 수 있고, 그 반대도 가능
메모리에 있는 값(bit)은 그대로 둔채, 포인터자료형만을 바꿔주는 기능
보통 low-level에서 사용
애초에 안전하지 않은 캐스팅으로, 왠만해선 다른 캐스팅 방법을 쓰는게 좋음
One_class* -> Unrelated_class* 처럼 관계없는 포인터 타입을 변환해주기에 안전하지 않음
그래서 애초에 반환값도 안전하지 않아 사용하기 힘듦
특히 상속관계의 클래스에는 꼭 static_cast나 dynamic_cast사용
int a = 1234;
char* p = reinterpret_cast<char*>(&a); // int* → char*
void* v = &a;
int* ip = reinterpret_cast<int*>(v); // void* → int*
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}
데이터 p의 메모리 주소값을 바로 unsigned int로 변환함
그리고 주소의 상위 16bit와 하위 16bit를 xor하여 hash로 사용
주소가 다르면 해시값이 같을 확률이 낮아 포인터를 바로 정수형으로 변환시켜 해시로 사용