종류
- static_cast()
- const_cast()
- reinterpret_cast()
- dynamic_cast()
- bit_cast () [C++20부터 추가됨)
C스타일 캐스트와 비교
C 스타일 캐스트도 네개의 C++ 캐스팅 기능을 모두 포함하지만
의도가 분명하게 드러나지 않아 에러가 발생하기 쉽고
예상과 다른 결과가 나올 수 있다.
따라서 C++스타일로 캐스트하는게 훨씬 안전하고 문법도 깔끔해진다.
1. static_cast()
얼어에서 제공하는 명시적 변환을 수행한다.
예를 들어 나눗셈 시 int를 부동소수점 형태로 변환 할 때이다.
int i { 3 };
int j { 4 };
double k = static_cast<double>(i) / j;
사용자 정의 생성자나 변환 루틴에서 허용하는 명시적 변환을 수행 할 때도 사용할 수 있다.
상위 타입을 하위 타입으로 다운 캐스팅 할 때도 사용된다.
class Base
{
public:
virtual ~Base() = default;
};
class Derived : public Base
{
public:
virtual ~Derived() = default;
};
int main()
{
Base* b{ nullptr };
Derived* d{ new Derived{} };
b = d; // 상속 뎨층의 상위 타입으로 업캐스트 할 필요없다.
d = static_cast<Derived*>(b); // 상속 계층의 하위 타입으로 다운캐스트해야 한다.
Base base;
Derived derived;
Base& br{ derived };
Derived& dr{ static_cast<Derived&>(br) };
}
이러한 캐스트는 포인터나 레퍼런스에 적용할 수 있고, 객체에 직접 적용할 수 없다.
※ 상위 타입에서 하위 타입의 다운 캐스팅은 예상치 못하는 오류가 발생할 수 있다. 그런 이유로 런타임에 안전하게 다운캐스팅이 가능한 dynamic_cast를 이용해야 한다.
static_cast()는 강력하지 않다. 서로 관련이 없는 타입의 포인터끼리는 적용이 불가능 하며 변환 생성자가 제공되지 않는 타입의 객체도 변환이 불가능 하고 int에 대한 포인터도 적용할 수 없다.
2. const_cast()
const_cast()는 변수에 const 속성을 제거하는 기능을 제공한다.
제공은 하지만 사용할 일이 없어야하지만 서드파티 라이브러리처럼 수정할 수 없는 경우에 부득이 const 속성을 일시적으로 제거할 수 밖에 없다.
#include <Tirdparty.h>
Tirdparty
{
float exec(Data d) { ... }
}
int Test(const Data& d)
{
Tirdparty t;
// 아래처럼 써드파티 라이브러리의 함수가 const를 안 받는 경우에는 const 캐스팅을 해줘야하는 상황이 발생
t.exec(const_cast<Data>(d));
}
반대로 표준 라이브러리는 std::as_const()란 헬퍼 메서드를 제공해준다. 이 메서드는 <utility>헤더에 정의되어 있으며, 레퍼런스 매개변수를 const 레퍼런스 버전으로 변환 해준다.
3. reinterpret_cast()
static_cast() 보다 강력하지만 안정성은 떨어진다. C++ 타입 규칙에서 허용하지 않더라도 상황에 따라 적합하다면 캐스트할 수 있다.
- 상속 계층이 아닌 경우
- void*를 다른 타입으로 캐스팅하는 경우
class X {};
class Y {};
int main()
{
X x;
Y y;
X* xp{ &x };
Y* yp{ &y };
// 서로 관련이 없는 클래스 타입의 포인터를 변환 할 때 reinterpret_cast를 사용
// static_cast 는 작동하지 않는다.
xp = reinterpret_cast<X*>(yp);
// 포인터를 void* 로 변환할 때는 캐스팅이 필요없다.
void* p{ xp };
// void*를 다른 타입으로 변환시 캐스팅이 필요하다.
xp = reinterpret_cast<X*>(p);
// 서로 관련없는 타입끼리의 변환시 reinterpret_cast를 사용해야한다.
// static_cast 는 작동하지 않는다.
X& xr{ x };
Y& yr{ reinterpret_cast<Y&>(x) };
}
reinterpret_cast도 제약 사항이 있다.
예를 들어 64비트 포인터를 int 타입으로 캐스팅을 시도하면 공간이 부족해 컴파일 에러가 발생한다.
4. dynamic_cast()
같은 상속 계층 내부에서 캐스트에 대한 실행 시간 검사를 제공한다.
포인터나 레퍼런스를 캐스트할 때 이를 이용할 수 있다.
dynamic_cast()는 내부 객체의 타입 정보를 실행 시간에 검사한다. 그러므로 캐스트하는 것이 적합하지 않다고 판단하면
포인터에 대해서는 널 포인터를 리턴하고 레퍼런스에 대해서는 std::bas_cast 익셉션을 발생시킨다.
올바른 사용 예제
class Base
{
public:
virtual ~Base() = default;
};
class Derived : public Base
{
public:
virtual ~Derived() = default;
};
Base* b;
Derived* d{ new Derived{} };
b = d;
d = dynamic_cast<Derived*>(b);
}
예외가 발생하는 예제
Base base;
Derived derived;
Base& br{ base };
try {
Derived& dr{ dynamic_cast<Derived&>(br) };
}
catch (const std::bad_cast&) {
std::cout << "Bad cast!" << std::endl;
}
}
static_cast()와 reinterpret_cast()는 위와 같은 상황에서도 컴파일 타임에 그냥 캐스팅을 해버리지만
dynamic_cast는 nullptr을 리턴하거나 예외를 발생시킨다.
실행 시간의 타임 정보는 가상 함수 테이블에 저장되며 RTTI에 기록되어있다.
따라서 dynamic_cast를 적용하려면 클래스에 하나 이상의 virtual 키워드가 적용된 메서드거 있어야 한다.
그렇지 않으면 컴파일 에러가 발생한다.
4. bit_cast()
표준 라이브러리에 속한 유일한 캐스트이다. 다른 캐스트는 C++언어의 일부이다.
주어진 타깃 타입으로 객체를 새로 만들어서 원본 객체에 있는 비트를 새 객체로 복사 한다.
bit_cast()를 사용할 때 원본 객체와 대상 객체의 크기가 서로 같으며 쉽게 복제가 가능해야 한다.
쉽게 복제가 가능한 타입이란 객체를 구성하는 바이트를 char들과 같은 타입의 배열로 복제할 수 있어야 한다는 뜻이다. |
참고
전문가를 위한 C++20
'프로그래밍 > CPP' 카테고리의 다른 글
[C++] 구조체 변수의 크기 (패딩 바이트) (0) | 2024.01.05 |
---|---|
객체 지향 개발 5대 원칙 (0) | 2024.01.04 |
[C++ STL] 스마트 포인터 (0) | 2023.12.22 |
엔진에 ENGINE_DLL까지 붙혔는데도 불안정한 형식이라면서 클래스 참조가 안되는 이유 (0) | 2023.04.08 |
json 파일에서 string을 읽어와서 동적 char 배열에 추가시 주의점 (0) | 2023.04.02 |