C++에서의 type casting에 대해 공부해보자

Yerin·2024년 7월 24일
post-thumbnail

C++ 공부가 시급한데.. 너무 안해서 큰일이다..
쩝.. 공부하는대로 하나씩 블로그 글 써야할 것 같다..
이펙티브 C++도 너무 어려워서 못읽겠음 ㅜㅜ

아무튼간 오늘은 C++의 타입캐스팅에 대해 얘기해보려고 한다.
앞서, 형변환시에는 데이터 변형이 발생할 수 있으므로 항상 유의해야한다.



먼저 C에서부터 사용되어왔던 형변환에 대해 얘기해보자.

C스타일 타입 캐스팅

암시적 형변환(Implicit Casting)

컴파일러가 자동으로 형변환을 수행한다. (컴퓨터가 알아서 자료형에 맞게 변환해주다는 뜻이다 ~)
사실 특별할 것은 없고, 서로 다른 자료형 간의 연산에서 결과값의 자료형을 지정하지 않으면 우선순위가 더 높은 자료형으로 저장된다는 특징이 있다.

예를들어 int + float을 하면 float 자료형으로 값이 저장된다.

int a = 3;
float b = a; // 이게 그냥 암시적 형변환임

float c = a + b; // 이건 정상동작
int c = a + b;   // 이건 정상동작하지 않음. float자료형으로 결과가 저장되기 때문

명시적 형변환(Explicit Casting)

프로그래머가 직접 형변환을 지정하는 것을 말한다.

int a = 3; 
float b = 3.0; 
int ans = a + (int)b;  // 아까 위에걸 이렇게 해결할 수 있다.

C++에서도 C언어와 같이 암식적 형변환과 명시적 형변환이 모두 지원된다.
그러나, C++에서는 고유의 형변환 연산자를 사용하는 것이 더 일반적이고 안전하다고 한다.


C++ 스타일 타입 캐스팅

이 아래 부분부터는 이펙티브 C++의 item27에서 casting에 대한 설명을 참고하여 작성하였다.

static_cast

암시적 변환 을 강제로 진행할 때 사용한다.
예 ) 비상수 객체 → 상수 객체, int 타입 → double 타입

타입 변환을 거꾸로 수행하는 용도로도 사용한다.
예) void* 타입 → 일반 타입의 포인터

int i = 10;
float f1 = static_cast<float>(i);
float f2 = (float)i;

설명만 보면 C스타일의 명시적 형변환과 C++ 스타일의 형변환이 다를게 없다고 생각할 수 있다. (적어도 나는 그랬다..)
두 방법 모두 컴파일 타임에 타입의 유효성을 검사하지만 , C 스타일 캐스트는 static, reinterpret, const, dynamic 캐스트를 모두 포괄하기 때문에 C++ 스타일 캐스트가 타입검사에 있어 더 안전하다고 볼 수 있다고 한다.


dynamic_cast

safe downcasting을 할 때 사용한다. 즉, 주어진 객체가 어떤 클래스 상속 계통에 속한 특정 타입인지 아닌지를 결정하는 작업에 쓰인다.
런타임에 안전성을 확인하면서 형변환을 수행하기 때문에 런타임 비용이 높은 연산자이다.

// 주로 상속 관계에 있는 클래스 간의 형변환에 사용된다.
class Base { virtual void func() {} };
class Derived : public Base { };

Base* b = new Derived;
Derived* d = dynamic_cast<Derived*>(b);

const_cast

객체의 상수성을 없애는 용도로 사용한다.
GPT의 설명을 덧붙이자면, const 또는 volatile 속성을 추가하거나 제거하는 데 사용된다고 한다.

const : 변수의 값을 변경할 수 없다.
→ 속성을 제거하면 값을 변경할 수 있다. 예) const int*int*

volatile : 컴파일러 최적화에 의해 제거되지 않고 항상 메모리에서 읽혀야한다. 주로 하드웨어 레지스터나 멀티스레딩 환경에서 사용.
→ 속성을 제거하면 컴파일러 최적화를 허용한다. (하드웨어와의 상호작용 시 유용할 수 있음)
예) volatile int*int*


reinterpret_cast

하부 수준 캐스팅을 위해 만들어진 연산자이다. 이 연산자의 적용 결과는 구현 환경에 의존적이다. (이식성이 없다는 뜻)
→ 이런 캐스트는 거의 쓰이지 않아야한다.

예) 포인터를 정수형으로 변환하여 그 포인터의 메모리 주소를 정수로 취급하는 경우
반대로 정수를 포인터로 변환하여 특정 메모리 주소를 가리키게 하는 경우

#include <iostream>

int main() {
    int num = 42;
    int* intPtr = &num;

    // 포인터를 정수로 변환
    uintptr_t intAddress = reinterpret_cast<uintptr_t>(intPtr);
    std::cout << "Pointer as integer: " << intAddress << std::endl;

    // 정수를 다시 포인터로 변환
    int* newPtr = reinterpret_cast<int*>(intAddress);
    std::cout << "Value at new pointer: " << *newPtr << std::endl;

    return 0;
}

한번에 이해가 가지 않아서 GPT의 설명을 덧붙인다.
아직 어떤 용도로 쓰이는건지는 잘 모르겠긴하다. 이것 때문에 type casting 공부한건데 (쩝.)

reinterpret_cast는 포인터 타입을 전혀 다른 포인터 타입으로 변환할 때 사용합니다.
주로 포인터 타입을 전혀 다른 포인터 타입으로 변환할 때 사용됩니다.
이는 데이터 타입을 바꾸지 않고 메모리 주소만 재해석합니다.
예를들어 저수준의 메모리 조작, 시스템 프로그래밍, 하드웨어 인터페이스 등에서 유용할 수 있습니다.



C++ 코드를 읽다보면 이런 형변환에서 가끔씩 뇌정지가 온다 ..ㅋㅋ (이유는 나도 몰라..)
이쪽 공부했으니까 다음 포스팅에서는 이펙티브 C++ 27번 아이템을 끝까지 읽어와보는 걸 적을 듯.. (아님 말고)


참고

C언어 기초 : 암시적 형변환과 명시적 형변환
Effective C++ item27. 캐스팅은 절약, 또 절약! 잊지 말자
그리고 GPT

profile
𝙸 𝚐𝚘𝚝𝚝𝚊 𝚕𝚒𝚟𝚎 𝚖𝚢 𝚕𝚒𝚏𝚎 𝙽𝙾𝚆, 𝙽𝙾𝚃 𝚕𝚊𝚝𝚎𝚛 !

0개의 댓글