일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- this call
- delete function
- std::vector
- 더 지니어스 양면포커
- pointer to member data
- conversion constructor
- c++ multi chatting room
- virtual destructor
- operator overloading
- std::cout
- increment operator
- virtual function
- std::endl
- virtual inheritance
- base from member
- C++
- return by reference
- constructor
- discord bot
- c++ basic practice
- placement new
- member function pointer
- suffix return type
- std::ostream
- vector size
- new&delete
- vector capacity
- virtual function table
- dynamic_cast
- diamond inheritance
- Today
- Total
I'm FanJae.
[C++] RTTI, Dynamic Cast 본문
※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.
1. RTTI (Run Time Type Information)
#include <iostream>
#include <typeinfo>
int main()
{
int n1 = 10;
auto n2 = n1; // n2의 타입은? int
const std::type_info& t1 = typeid(n2);
std::cout << t1.name() << std::endl;
}
- 실행시간에 타입의 정보를 얻을 때 사용하는 기술이다.
1-1. RTTI 기술의 사용법
- <typeinfo> 헤더를 사용한다.
- typeid 연산자를 사용한다.
- 타입의 정보를 담은 type_info 객체를 얻을 수 있다.
- type_info 객체의 멤버 함수 name()을 사용
① Typeid
#include <iostream>
#include <typeinfo>
int main()
{
auto n1 = 10;
typeid(n1);
}
- typeid는 타입에 대한 정보를 얻을 때 사용하는 연산자이다.
- 다양한 형태로 사용 가능하다.
- typeid 연산자의 결과로 const std::type_info&가 반환된다.
typeid(변수) | typeid ( n1 ) |
typeid(타입) | typeid ( int ) |
typeid(표현식) | typeid ( 3 + 4.2 ) |
② std::type_info
- 타입 정보를 담고 있는 클래스이다.
- 사용자가 직접 객체를 만들 수는 없고, typeid() 연산자를 통해서만 얻을 수 있다.
// std::type_info t2;
const std::type_info& t = typeid(3 + 4.2);
- 위와 같이 직접 객체를 만드는 경우는 컴파일 에러가 발생한다.
- 또한, typeid() 연산자를 통해 얻어온다고 하더라도 type_info 상수 참조로만 얻어올 수 있다.
③ type의 비교
const std::type_info& t1 = typeid(n1);
const std::type_info& t2 = typeid(int);
if ( t1 == t2 )
{
std::cout << "n1은 int" << std::endl;
}
if ( typeid(n1) == typeid(int) )
{
std::cout << "n1은 int" << std::endl;
}
- 위 2개의 if는 논리적으로 동일하다.
- 따라서 실제로는 아래 코드가 더 많이 사용된다.
2. Dynamic_cast
2-1. Downcasting
#include <iostream>
#include <typeinfo>
class Animal{};
class Dog : public Animal
{
public:
int color;
};
void foo(Animal* p)
{
// p가 Dog라면?
p->color = 10;
}
int main()
{
Animal a; foo(&a);
Dog d; foo(&d);
}
- 함수가 인자로 기반 클래스의 포인터를 받으면, 모든 파생 클래스의 전달도 가능하다.
- 문제는 기반 클래스 포인터로 파생 클래스의 고유 멤버에 접근할 수 없다는 문제가 있다.
※ 이에 따라서, 파생 클래스의 고유 멤버에 접근하려면 파생 클래스 타입으로 캐스팅(Downcasting) 해야 한다.
void foo(Animal* p)
{
// p가 Dog라면?
const std::type_info& t = typeid(*p);
std::cout << t.name() << std::endl;
}
- 이와 같이 수정하더라도, typeid는 객체 a,d에 대해서 모두 Animal 타입으로 인지한다.
- 그 이유는 가상함수가 없는 객체(non polymorphic type)는 컴파일 시간에 포인터 타입으로 조사한다.
- 이에 반면, 가상함수가 있는 객체(polymorphic type) 는 실행시간 타입으로 조사한다. 즉, 가상함수 테이블을 사용.
※ 즉, RTTI를 제대로 사용하기 위해서는 기반 클래스가 1개 이상의 가상함수를 가지고 있어야 한다. 보통은 가상 소멸자가 존재하기 떄문에, 문제가 되지 않는다.
class Animal
{
public:
virtual ~Animal() {}
};
- 이를 정리하여, 다음과 같이 정리 해본다.
#include <iostream>
#include <typeinfo>
class Animal{};
class Dog : public Animal
{
public:
int color;
};
void foo(Animal* p)
{
const std::type_info& t = typeid(*p);
std::cout << t.name() << std::endl;
if (typeid(*p) == typeid(dog))
{
Dog* pDog = static_cast<Dog*>(p);
pDog->color = 10;
std::cout << "Dog" << std::endl;
}
}
int main()
{
Animal a; foo(&a);
Dog d; foo(&d);
}
2-2. static_cast의 문제점
#include <iostream>
#include <typeinfo>
class Animal{};
class Dog : public Animal
{
public:
int color;
};
void foo(Animal* p)
{
Dog* pDog = static_cast<Dog*>(p);
std::cout << pDog << std::endl;
}
int main()
{
Animal a; foo(&a);
Dog d; foo(&d);
}
- 위 함수 foo()를 보면, Animal 클래스의 포인터를 Dog로 변환하는 시도를 하고 있다. 이는 안전하지 않을 수 있다.
- 여기서, downcasting과 캐스팅 연산자에 차이에 대해서 보고 정리한다.
① upcasting vs downcasting
upcasting | 파생 클래스 포인터를 기반 클래스 타입으로 캐스팅하는 것. 항상 안전하다. |
downcasting | 기반 클래스 포인터를 파생 클래스 타입으로 캐스팅하는 것. 안전하지 않을 수도 있다. |
② downcasting과 캐스팅 연산자
static_cast | 잘못된 downcasting을 조사할 수 없다. 단, 컴파일 시간에 캐스팅을 수행하므로 오버헤드가 없다. |
dynamic_cast | 잘못된 downcasting을 하면 0을 반환한다. 실행 시간에 캐스팅을 수행하므로 약간의 오버헤드가 있다. |
- 정리 하면, static_cast는 위 downcasting이 잘못된 것인지 판단할 수가 없다. 따라서, 정상 실행 될수도 있다.
- 또는 이것이 runtime error를 발생할 수도 있다. 즉, 위험한 코드이다.
void foo(Animal* p)
{
Dog* pDog = dynamic_cast<Dog*>(p);
if ( pDog != 0 )
{
pDog->Color = 10;
}
std::cout << pDog << std::endl;
}
- 위와 같이 처리해주어야 한다. 단, dynamic_cast도 반드시 1개 이상의 가상함수를 가지고 있어야 사용할 수 있다.
- dynamic_cast가 꼭 좋은 것은 아니지만, foo()에 들어가는 객체가 Dog 타입임을 장담할 수 없다면, 이와 같이 처리한다.
'C++ > Basic' 카테고리의 다른 글
[C++] Operator Overloading I (0) | 2024.08.23 |
---|---|
[C++] Multiple Inheritance, Diamond Inheritance, Virtual Inheritance (0) | 2024.08.23 |
[C++] Virtual Function Table (0) | 2024.08.22 |
[C++] Abstract class, Interface (0) | 2024.08.21 |
[C++] Upcasting, Virtual Function (0) | 2024.08.21 |