프로그래밍/CPP

[C++] 캐스팅 연산자

스스배 2024. 1. 3. 21:06

종류

- 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