모든 언어에는 형변환이 있습니다. C++에선 다양한 형번환 객체들을 제공합니다.
1. static_cast = https://hwan-shell.tistory.com/211
2. dynamic_cast
3. const_cast = https://hwan-shell.tistory.com/215
4. reinterpret_cast = https://hwan-shell.tistory.com/219
가 있습니다.
그 중 dynamic_cast에 대해 알아보도록 하겠습니다.
1. dynamic_cast란?
class의 상속관계에서의 형변환을 프로그래머가 올바르게 하도록 도와주는 긴능을 제공하는 녀석입니다.
dynamic_cast는 RTTI(Run Time Type Information)을 지원합니다.
RTTI는 런타임에서 클래스의 type_info를 보고 해당 클래스가 올바른 type의 형태인지 아닌지 판단하게 해 줍니다.
dynamic_cast를 사용하는 1가지 조건이 있습니다.
바로 virtual function을 사용해야 합니다.
그 이유는 RTTI의 type_info 때문입니다.
virtual function을 사용하게 되면 해당 클래스에는 v-table이 생성이 됩니다.
이 v-table에는 override된 자식 클래스의 함수를 가르킬 수 있는 주소값이 들어있는데
이 v-table에 클래스의 type_info 정보가 들어가게 됩니다.
때문에 dynamic_cast를 사용하게 되면 좀 더 안전한 형변환을 할 수 있습니다.
하지만 단점은 RTTI는 자원을 좀 먹기 때문에 퍼포먼스 측면에서 static_cast보다 좀 떨어지는 것이 사실입니다.
(a little....)
그럼 dynamic_cast를 왜 써야 할까?? 도대체 쓸 일이 있을까??
형변환?? 그런걸 할 이유가 있나?? 언제 필요하나?? 에 대해 궁금할 수 있습니다.
때문에 dynamic_cast사용법과 더불어 사용하는 예시를 하나 소개해보겠습니다.
2. 상속 upcast, downcast 사용 예
저는 Animal이라는 부모 클래스를 만들고 자식 클래스로 Cat 과 Dog를 만들고 싶습니다.
그리고 Cat 객체와 Dog객체 각 100개씩 총 200개를 백터에 넣어 관리하고 싶습니다.
하지만 저는 vector<Cat>, vector<Dog>
이렇게 2개의 백터로 관리하고 싶지 않고
하나의 백터로 관리하고 싶습니다.
이럴땐 어떻게 하면 될까요??
#include <iostream>
#include <string>
#include <vector>
class Animal {
public:
virtual void sound() = 0;
void info(){
std::cout << "동물은 숨을 쉽니다.\n";
}
};
class Dog : public Animal{
private:
std::string name;
public:
Dog(std::string s) : name(s) {};
void sound() { std::cout << "멍멍\n"; }
void name_print() { std::cout << name << std::endl; }
void only_dog() { std::cout << "이건 개 클래스\n"; }
};
class Cat : public Animal {
private:
std::string name;
public:
Cat(std::string s) : name(s) {};
void sound() { std::cout << "냐옹\n"; }
void name_print() { std::cout << name << std::endl; }
void data() { std::cout << this << std::endl; }
void only_cat() { std::cout << "이건 고양이 클래스\n"; }
};
int main() {
std::vector<Animal*> v;
v.emplace_back(new Cat("나비"));
v.emplace_back(new Dog("멍멍이"));
delete v[0];
delete v[1];
return 0;
}
이런식으로 Animal이라는 부모객체를 선언 후 upcast를 통해 자식 클래스를 관리할 수 있습니다.
그럼 빼낼 땐 어떻게 해야 할까요?? downcast를 해주면 됩니다.
int main() {
vector<Animal*> v;
v.emplace_back(new Cat("나비"));
v.emplace_back(new Dog("멍멍이"));
Cat* cat = static_cast<Cat*>(v[0]);
Dog* dog = (Dog*)v[1];
cat->name_print();
cat->sound();
dog->name_print();
dog->sound();
return 0;
}
downcast가 잘 된것을 확인할 수 있습니다.
하지만 문제가 있습니다.
백터에 들어가있는 클래스는 Cat클래스 인데 Dog클래스로 받으면??
심각한 오류를 범할 수 있습니다.
int main() {
vector<Animal*> v;
v.emplace_back(new Cat("나비"));
v.emplace_back(new Dog("멍멍이"));
Dog* cat = static_cast<Dog*>(v[0]);
Cat* dog = (Cat*)v[1];
cat->name_print();
cat->sound();
cat->only_dog();
dog->name_print();
dog->sound();
dog->only_cat();
return 0;
}
Dog와 Cat의 위치를 바꿔줬는데 실행이 잘 되는걸 확인할 수 있습니다.
하지만 실행 결과는 혼종이 탄생되었습니다.
이런 오류때문에 이런것들을 방지하고자 dynamic_cast
를 사용합니다.
3. dynamic_cast 사용법
#include <iostream>
#include <string>
#include <vector>
class Animal {
public:
virtual void sound() = 0;
void info(){
std::cout << "동물은 숨을 쉽니다.\n";
}
};
class Dog : public Animal{
private:
std::string name;
public:
Dog(std::string s) : name(s) {};
void sound() { std::cout << "멍멍\n"; }
void name_print() { std::cout << name << std::endl; }
void only_dog() { std::cout << "이건 개 클래스\n"; }
};
class Cat : public Animal {
private:
std::string name;
public:
Cat(std::string s) : name(s) {};
void sound() { std::cout << "냐옹\n"; }
void name_print() { std::cout << name << std::endl; }
void data() { std::cout << this << std::endl; }
void only_cat() { std::cout << "이건 고양이 클래스\n"; }
};
int main() {
std::vector<Animal*> v;
v.emplace_back(new Cat("나비"));
v.emplace_back(new Dog("멍멍이"));
Cat* cat; Dog* dog;
for (size_t idx = 0; idx < v.size(); idx++) {
if (cat = dynamic_cast<Cat*>(v[idx])) {
cat->name_print();
cat->sound();
cat->only_cat();
}
else {
dog = dynamic_cast<Dog*>(v[idx]);
dog->name_print();
dog->sound();
dog->only_dog();
}
}
return 0;
}
이런식으로 if문을 통해 Runtime에 클래스들의 타입에 맞게 downcast된 것을 확인할 수 있습니다.
안정적인 코드를 위해서 dynamic_cast
를 사용하는 것이 좋습니다.