bdfgdfg

[C++] C++의 4가지 캐스팅(형변환) 본문

게임프로그래밍/C++

[C++] C++의 4가지 캐스팅(형변환)

marmelo12 2022. 1. 17. 23:10
반응형

캐스팅(형변환)

 - 하나의 데이터형을 다른 데이터형으로 변환하는 것.

 

기존의 C스타일의 형변환은 밑과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Test
{
private:
    int m_test = 10;
};
 
int main()
{
    int a = 5;
    long long b = a; // 묵시적 형변환. 컴파일러가 형을 변환해준다.
    // -> 형변환이 허용되며 프로그래머가 명시적으로 형변환을 안한경우.
    Test* t = (Test*)&a; // 명시적 형변환. 프로그래머가 형변환을 위한 코드를 직접 작성.
 
    return 0;
}
cs

 

C++스타일의 형변환 연산자는 4가지가 존재한다.

 

1. static_cast

2. reinterfret_cast

3. const_cast

4. dynamic_cast

 

C스타일의 캐스팅은 C++의 캐스팅 연산자 4개중 하나를 한다.

 -> 어떤 형변환인지 명확하지 않다. (형변환의 의도를 구별하기 힘들다)

 -> 명백한 실수를 컴파일러가 캐치하지 못할수도 있다.

 

그렇기에 좀 더 안전하고, 사용자의 의도를 좀 더 나타내기 위한 C++ 스타일의 형변환을 사용한다.

 

static_cast

정적 캐스트. 보통 정적이라 함은 언어에서 컴파일시에~ 동적이라함은 런타임시에~를 의미한다.

static_cast 연산자를 통해 형변환을 하면 컴파일(정적) 타임에 형변환이 가능한지 검사한다.

기본 자료형간의 형변환도 허용하며, 이 때 최대한 값을 유지.

 -> 이진수 표기또한 달라질 수 있다. ((float -> int등)

1
2
3
4
float a = 50.545f;
int b = 60;
= static_cast<int>(a);
std::cout << b; // 50이 출력된다.
cs

위의 C스타일 캐스팅의 예제에서 Test 포인터 변수에 강제로 int형 변수의 주소를 Test*로 형변환하여 대입했다.

이것을 static_cast를 이용하여 형변환이 가능한지 실험해보면.

위와 같이 컴파일 에러를 내뱉는다.

 - 컴파일 에러(컴파일러가 이해하지 못하는 코드를 발견할 때 발생하는 오류)

 

이렇게 포인터 타입을 다른것으로 변환 하는것을 허용하지 않는다.(컴파일 에러 발생)

다만 상속 관계에 있는 클래스 타입의 형변환은 허용한다.

 - 부모 클래스의 참조/포인터 형식에서 자식 클래스의 참조/포인터 형식으로의 형변환 허용.(unsafe)

  -> 다운캐스팅이라 한다.(부모 -> 자식으로 형변환)

 - 반대로 자식 클래스의 참조/포인터 형식에서 부모 클래스의 참조/포인터 형식으로의 형변환 허용.(safe)

  -> 업캐스팅이라 한다.(자식 -> 부모로 형변환)

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
31
class Base
{
public:
    void Print()
    {
        std::cout << "I'm Base " << m_base << std::endl;
    }
private:
    int m_base = 50;
};
 
class Derived : public Base
{
public:
    void Print()
    {
        std::cout << "I'm Derived " << m_derived << std::endl;
    }
private:
    int m_derived = 100;
};
 
int main()
{
    Base* b;
    Derived* d = new Derived();
    // 업 캐스팅.(더 작은 메모리의 부모가 더 큰 자식의 메모리를 가르킨다. - safe)
    b = static_cast<Base*>(d); 
    b->Print();
delete d;
    return 0;
}
cs

결과.

반대로 다운 캐스팅을 보면.

1
2
3
4
5
6
7
8
9
int main()
{
    Base* b = new Base();
    Derived* d;
    // 다운 캐스팅.(더 큰 메모리의 자식이 더 작은 부모의 메모리를 가르킨다. - unsafe)
    d = static_cast<Derived*>(b);
    d->Print();
    delete b;
}
cs

이상한 쓰레기값이 출력된다.

-> 부모는 자식 클래스의 멤버를 가지고 있지 않기에 크래시가 날수도 있음.

즉 이렇게 static_cast는 형변환의 허용범위가 넓지만 상속관계에서의 형변환은 그다지 안전하지 않다.

 

static_cast연산자는 일반적으로 4가지 형변환 연산자중 가장 사용빈도가 높다.

 - 다른 형변환 연산자와는 달리 기본 자료형의 형변환을 허용하므로

 - 밑에서 설명할 dynamic cast는 기본 자료형간의 형변환이 불가능하고 static_cast에 비해 성능이 떨어짐.

 - reinterpret_cast와 const_cast는 사용예제가 다름.

 

reinterpret_cast

이 형변환 연산자는 일반적으로 허용하지 않는 형변환을 강제적으로 형변환할 때 사용한다.

 - 포인터/참조와 관련된 형변환만 지원한다.

 - 연관없는 두 포인터 형 사이의 변환을 허용

 - 이름에서 알 수 있듯이 말 그대로 재해석(reinterpret)한다고 볼 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Animal
{
private:
    int m_age;
};
 
class Human
{
private:
    int m_age;
};
int main()
{
    Animal* animal = new Animal();
    Human* human = new Human();
    human = static_cast<Human*>(animal); // 컴파일에러.
    human = reinterpret_cast<Human*>(animal); // 형변환 허용!
    delete animal;
    delete human;
}
cs

위와 같이 전혀 관계가 없는 클래스간 포인터타입으로의 형변환도 허용한다.

 

의미는 간단하지만 잘못 사용할시 쉽게 문제가 발생할 수 있는 형변환 연산자.

이런 형변환 연산자가 사용되는 경우는 가끔 라이브러리의 함수의 자료형을 형변환 할때가 있는데 그럴때 자주 사용된다. 예시를 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct LibMatrix4x4 // 어떤 라이브러리 구조체
{
    float _11, _12, _13, _14;
    float _21, _22, _23, _24;
    float _31, _32, _33, _34;
    float _41, _42, _43, _44;
};
 
struct MyMatrix // 사용자 정의 구조체
{
    float m[4][4];
};
 
int main()
{
   LibMatrix4x4 libMatrix;
    float* a = (float*)&libMatrix;
    for (int i = 0; i < 16++i)
        *(a + i) = (i + 1.0f); // 1 ~ 16 
}
cs

위와 같이 불편하게 하나하나 접근해야 하는 구조체의 멤버를 사용자가 정의한 행렬 구조체로 형변환하여 쉽게 사용가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
    LibMatrix4x4 libMatrix;
    float* a = (float*)&libMatrix;
    for (int i = 0; i < 16++i)
        *(a + i) = (i + 1.0f); // 1 ~ 16 
 
    MyMatrix* matrix = reinterpret_cast<MyMatrix*>(&libMatrix);
 
    for (int y = 0; y < 4++y)
    {
        for (int x = 0; x < 4++x)
            std::cout << matrix->m[y][x] << " ";
        std::cout << std::endl;
    }
    return 0;
}
cs

결과

잘못사용하면 위험한 형변환 연산자이지만 위의 간단한 예시처럼 유용하게 사용될일도 존재한다.

 

 

const_cast

- 이 형변환 연산자는 다른 자료형으로 바꿀수는 없다. 

- const 또는 volatile이 붙은 자료형의 속성을 제거할 때 사용된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
    const char* any = "Hello";
    char* temp = const_cast<char*>(any);
    
    Animal* myAnimal = new Animal();
    const Animal* cstPtr = myAnimal;
    Animal* ptr = const_cast<Animal*>(cstPtr);
    
    delete myAnimal;
    
    return 0;
}
cs

가장 간단한 개념이지만,

const라는 의미는 객체가 변하지 말아야한다는 코드작성자의 의도를 컴파일러와 다른 사람에게 알려주는 유용한 수단이므로 const_cast를 이용하여 const 속성을 제거하는건 옳지 못하다.

다만 라이브러리의 함수 인자가 const기능이 없고 const 속성이 존재하는 변수를 넘기지 못하는 경우.

1
2
3
4
5
6
void LibFunc(char* ptr) { ~~~ }
 
void MyFunc(const char* ptr)
{
    LibFunc(const_cast<char*>(ptr));
}
cs

(간단한 예시)

위와 같은 예시가 아니라면 const를 해제하는 일은 가급적 자제해야 한다.

 

 

dynamic_cast

- static_cast와 달리 컴파일 시점이 아닌 런타임시에 형변환을 검사하여 형변환을 보다 안전하게 처리한다.

 -> 실행중에(런타임) 자료형을 판단

 -> 포인터 또는 참조형을 캐스팅할 때만 사용가능.

 -> 정확히는 부모 클래스와 자식 클래스간의 변환에 사용된다. (실패시 0을 반환)

  -> 하나 이상의 virtual함수가 존재해야 한다. 

 -> 호환되지 않는 자식형으로 캐스팅하려하면 NULL반환 (다운 캐스팅 SAFE)

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
class Animal
{
public:
    Animal() { }
    virtual ~Animal() { }
};
 
class Cat : public Animal
{
public:
    Cat() { }
    ~Cat() { }
};
 
class Dog : public Animal
{
public:
    Dog() { }
    ~Dog() { }
};
 
int main()
{
    Animal* cat = new Cat();
 
    Dog* dog = dynamic_cast<Dog*>(cat);
    delete cat;
}
cs

위와 같이. cat객체를 생성하고 그 객체를 Animal타입이 가르키고 있을 때 자식으로 형변환하는 경우.

cat포인터 변수가 가르키고 있는것은 Cat객체. dynamic_cast를 통해 Dog*로 형변환할 시

NULL을 반환한다.

제대로 Cat으로 형변환을 할 시

이렇게 형변환이 안전하게 가능해진다.

 

하지만 이게 가능한 이유는 RTTI(실시간 타입 정보)기능 덕분인데

출처 - https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=rhkdals1206&logNo=221559828570

성능을 중요시하는 c++에서는 속도의 저하를 유발하는 RTTI는 끄는게 보통이라 한다.

 

 

 

 

 

 

반응형

'게임프로그래밍 > C++' 카테고리의 다른 글

[C++11] default,delete,final,override 키워드  (0) 2022.01.22
[C++ 11] auto,nullptr  (0) 2022.01.19
[C++] 템플릿  (0) 2022.01.16
[C++] 연산자 오버로딩  (0) 2022.01.15
[C++ 객체지향] 다형성(Polymorphism)  (0) 2022.01.09
Comments