Type Casting ( 형 변환 )에는 몇 가지 종류가 있다.


C 스타일(강제 형변환)



C언어에서 사용되는 형변환 방식이며, C++에서는 타입 안정성 등의 이유로 권장하지 않는 방법이다.

잘못된 형변환 시도로 인해 런타임 시 오류가 발생할 가능성이 존재하기 때문이다.

그럼에도 불구하고 여전히 사용되고 있는 방식이다.



static_cast


컴파일 타임에 형 변환을 검증하는 방식이며, C 스타일 강제 형 변환과 큰 차이는 없다.

다운 캐스팅가능 여부를 체크하지 않고 즉시 형변환을 수행하기 때문에

이로 인해 발생하는 오류는 전적으로 개발자의 책임이다.

따라서 다운 캐스팅 시 주의해야 하며, 불안하다면 dynamic_cast의 사용을 고려해보자.

 


dynamic_cast


주로 다형성을 사용하는 클래스(적어도 하나 이상의 가상 함수를 가지는 클래스를 의미)에서

특정 타입의 포인터나 참조를 다른 타입으로 안전하게 다운 캐스팅하는 데 사용된다.


잘못된 형변환을 방지하기 위해 런타임 시 형변환 가능 여부를 검증하고 가능한 경우 형변환을 수행한다.


런타임 중 타입 식별하여 위해 RTTI (Runtime Type Information)를 사용한다.

RTTI는 컴파일러에 의해 제공되며,

클래스에 대한 정보를 저장하고 런타임에 그 정보에 액세스할 수 있게 해준다.


dynamic_cast는 이 정보를 사용하여 실제 객체의 타입을 확인하고 적절한 형변환을 수행한다.



C++의 RTTI 시스템은 주로 두 가지 구성 요소로 이루어져 있다

 

  • type_info 객체

    : 이는 특정 클래스에 대한 정보를 담고 있는 객체다.

    type_info 객체는 클래스의 이름 등과 같은 정보를 포함하고 있다.


  • vptr와 vtable

    : 각 객체는 vptr(virtual pointer)를 가지며, 

    이는 해당 객체의 클래스에 대한 vtable(virtual table)을 가리킨다.

    vtable은 해당 클래스의 가상 함수에 대한 포인터들을 담고 있는 테이블이며,

    RTTI가 활성화된 경우에는 type_info 객체에 대한 포인터도 포함한다.


dynamic_cast의 안정성 검증 과정


dynamic_cast가 수행되면, 컴파일러는 먼저 소스 객체의 vptr을 통해 vtable에 액세스한다. 

그런 다음, vtable의 type_info 포인터를 사용하여 소스 객체의 실제 타입을 얻는다. 

이 type_info 정보는 형변환 대상 타입의 type_info 정보와 비교한다.


타입 체크는 다음 2가지 방식이 있다.

  • dynamic_cast가 업캐스팅(upcasting, 즉, 파생 클래스에서 기본 클래스로의 변환)을 수행하는 경우, 

    이는 항상 안전하므로 dynamic_cast는 해당 변환을 바로 수행한다.


  • dynamic_cast가 다운캐스팅(downcasting, 즉, 기본 클래스에서 파생 클래스로의 변환) 또는 

    크로스 캐스팅(서로 관련 없는 클래스 간의 변환)을 수행하는 경우, 

    dynamic_cast는 먼저 해당 변환이 가능한지를 RTTI를 통해 확인한다.

    이 과정을 통해 dynamic_cast는 안전한 타입 변환을 보장하며, 

    이는 실행 시간에 수행되므로 런타임 비용이 발생한다. 

    따라서, dynamic_cast는 필요한 경우에만 사용되어야 하며, 

    가능하다면 static_cast나 다른 타입 변환 연산자를 사용하는 것이 성능에 더 유리할 수 있다.

예를 들면 하위 객체에는 존재하지만 상위 객체에는 존재하지 않는 필드나 메스드가 있을 수 있다.

이때 상위 객체를 하위 객체로 다운 캐스팅할 경우 문제가 발생할 수 있다.

이런 내용들을 런타임 시에 검증하는 역할을 한다.

 


reinterpret_cast

 
포인터를 다른 타입의 포인터로 강제 변환하거나,

정수 값을 포인터로 변환할 수 있다. (반대로도 가능)

임의의 데이터를 임의의 주소로 변환하는 방법이기 때문에
잘못 사용하게 될 경우에는 예상하지 않는 결과를 초래할 수 있으므로 주의해야 한다.
const와 volatile 관련해서는 쓸 수 없다.




const_cast



타입에 const 키워드를 붙이거나 뗄 때 사용된다. 

그 외에 volatile 키워드에도 사용될 수 있다. 역시 컴파일 타임에 타입을 확인한다.

class LinkingContext 
{
public:
    LinkingContext():mNextNetworkId(1)    {    }

    uint32_t GetNetworkId(const GameObject* inGameObject, bool inShouldCreateIfNotFound) 
    {
        auto it = mGameObjectToNetworkIdMap.find(const_cast<GameObject*>(inGameObject));
    }
    
private:
    std::unordered_map<uint32_t, GameObject*> mNetworkIdToGameObjectMap;
    std::unordered_map<GameObject*, uint32_t> mGameObjectToNetworkIdMap;
    uint32_t mNextNetworkId;
};






static_pointer_cast



shared_ptr를 대상으로 형 변환을 수행한다.


public 상속 시의 형변환


public으로 상속되었다면, 업 캐스팅 시 아래 예시 처럼 형변환이 가능하다.

const_cast를 제외하고는 모두 가능한 것을 볼 수 있다.

 

Protected 상속 시의 형변환

Protected로 상속이 이루어졌을 경우에는 아래 그림과 같은 결과가 나온다.


위의 코드에서 a = (BaseClass*)b; 와 같은 C 스타일 강제 형 변환은 편하지만 오류가 발생할 확률이 높다.

Protected 상속 시의 업 캐스팅은 C 스타일 강제 형 변환 또는 reinterpret_cast를 통해서만 가능하다.


하지만 C 스타일의 강제 형 변환이나 reinterpret_cast의 경우 매우 low level에서의 형변환을 수행한다.

즉, 객체를 바이트 수준에서 재해석하기 때문에 많은 오류의 원인이 되므로 사용에 주의해야 한다.


이러한 상황에서 가장 안전하고 적절한 방법은 변환을 위한 멤버 함수를 제공하는 것이다.

예시)

class Base {};

class Derived : private Base {
public:
    Base* toBase() { return this; }
};



// ...

Derived derived;
Base* base = derived.toBase();

'C++ > C++' 카테고리의 다른 글

길이가 일정하지 않은 문자열 다루기 stringstream  (0) 2021.03.13
람다 lambda  (0) 2021.03.12
메모 (작성 중)  (0) 2021.03.07
std::string 을 const char* 로 전달하기  (0) 2021.03.05
(작성 중)연산자 다중 정의  (0) 2021.03.05
Posted by Elan
: