[C++] 명시적인 C-style cast와 static_cast의 차이점

암시적인 형 변환과 명시적인 형 변환

암시적인 형 변환( type conversion )은 변수의 타입이 일치하지 않는 경우, 컴파일러가 자동으로 필요한 타입으로 변경하는 것을 말합니다.

 

아래의 예제가 암시적인 형 변환을 보여줍니다.

int main(){

    double val = 10 / 4;	// 암시적인 형 변환

    cout << "double type value: " << val << endl;
}

▼출력

double type value: 2

컴파일러는 10 / 4의 int 값을 double 타입의 변수에 대입하기 위해서, 자동으로 double 값으로 타입을 변경합니다.

 

그런데, 문제가 좀 있네요.

val의 값이 2.5가 되기를 기대했지만, 실제는 2가 되었습니다.

이렇게 된 이유는 10 / 4의 int 연산 수행을 하고, 그 결과를 double 타입으로 변경했기 때문입니다.

 

따라서, 10이나 4를 ( 혹은 10과 4를 모두 ) double 타입으로 변경하면 나누기의 결과 값이 2.5가 될 것입니다.

( 10이나 4를  double 타입으로 변경하면, C의 연산 규칙에 따라 나머지 숫자가 double 타입으로 변경됩니다. )

 

이렇게, 사용자가 컴파일러에게 직접 타입의 변환을 요청하는 것을 명시적인 형 변환( explicit type conversion )이라고 합니다.

 

C++에서는, int를 double 타입으로 변경하는 명시적인 방법으로 C-style 형 변환과 static_cast 연산자를 제공합니다.

int main(){

    double val1 = (double)10 / 4;    // C-style cast

    double val2 = double(10) / 4;   // 함수 style 형 변환

    double val3 = static_cast<double>(10) / 4;  // C++ style 형 변환

    cout << "double type value1: " << val1 << endl;
    cout << "double type value2: " << val2 << endl;
    cout << "double type value3: " << val3 << endl;
}

첫 번째 val1을 계산한 방법이 예전부터 사용되어 온 C-style cast입니다.

이 방식은 다음과 같이 선언합니다.

( 변경하고자 하는 타입 )값을 나타내는 표현식;

 

그리고, 이 방식을 함수 호출 방식으로 변경한 방법이 두 번째 val2를 계산한 방법입니다.

변경하고자 하는 타입( 값을 나타내는 표현식 );

이 방법은 위의 방식과 같은 기능을 수행합니다.

 

그리고, 마지막에선 val3를 계산한 방법으로 static_cast 연산자를 사용하는 법을 보여주고 있습니다.

static_cast 사용법은 다음과 같습니다.

static_cast< 변경하고자 하는 타입 >( 값을 나타내는 표현식 );

 

다음 예제에선, 함수의 값을 명시적으로 변경하는 방법을 보여줍니다.

int add( int a, int b){
    return a + b;
}

int main(){

    double val = static_cast<double>( add(4, 6) );

    cout << "double type value: " << val << endl;
}

이러한 명시적인 형 변환은 원래의 표현식의 값을 변경하는 것이 아니라, 원래의 표현식의 값을 입력값으로 해서 임시적인 변환 값을 생성해서 그 값을 사용하는 것입니다.

위의 예문에서는 표현식의 int 값으로부터 double 타입의 임시의 값 10.0을 생성합니다.

그리고, 그 결과를 double 타입의 변수에 대입하는 것입니다.

 

그런데, C++은 왜 C-style의 형 변환 방식을 놔두고, static_cast 연산자를 도입했을까요?

실제 사용방법도 C-style이 편해 보이는데 말입니다.

 

C-style cast와 static_cast

C++에서는 명시적인 형 변환에 static_cast 외에도 const_cast와 reinterpret_cast, 그리고 dynamic_cast 이렇게 세 가지 연산자를 더 제공합니다.

 

그런데, const_cast와 reinterpret_cast는 제한적인 사용을 해야 할 정도로 프로그램을 위협하는 방법을 제공합니다.

 

const_cast 연산자

const_cast는 한 변수를 참조하는 변수의 상수 속성을 제거하는 연산자입니다.

좀 더 정확하게 쓰자면, 상수 변수의 상수 속성을 제거하는 것이 아니라,  

상수가 아닌 변수를 참조하는, 상수 참조의 상수 속성을 제거하는 것이 목적인 연산자입니다.

 

다음 예제는 const_cast를 사용법을 보여줍니다.

void func_const_parameter( const int& not_changed){

    int& can_changed = const_cast<int&>(not_changed);
    can_changed += 10;
}

int main(){

    int val = 10;

    func_const_parameter(val);

    cout << "not changed val: " << val << endl;
}

▼출력

not changed val: 20

 

이 예제를 보면 알 수 있듯이, 이런 것이 가능하다면 const 키워드를 굳이 사용해야 할 필요가 있을까라고 물어보게 됩니다.

그렇지만, mutable 키워드를 사용할 곳이 있는 것처럼, 특수한 경우에 편리할 수도 있습니다.

 

mutable는 const 멤버 함수 내에서 멤버 변수의 값을 변경할 수 있도록 만들어 주는 키워드입니다.

mutable에 관한 내용은 여기에서 볼 수 있습니다.

 

[C++] const 속성과 mutable 키워드

const 멤버 함수와 mutable 키워드const 멤버 함수는 함수 내에서 const 변수뿐만 아니라, 일반 변수의 값도 바꿀 수 없도록 제한을 건 클래스 멤버 함수입니다.그리고, const 멤버 함수 내에서는 멤버 변

codingembers.tistory.com

 

reinterpret_cast 연산자

reinterpret_cast는 한 포인터 타입을 다른 포인터 타입으로 변환하는 연산자입니다.

그런데, 이 연산자의 문제점은 이러한 변환이 아무 관련이 없는 타입의 포인터 간에도 수행이 가능하다는 겁니다.

 

다음 예제에서 reinterpret_cast의 사용법을 보여줍니다.

struct objA{
    void doSomething(){
        // do something
    }
};

struct objB{
    void doSomething(){
        // do something have nothing to do objA
    }
};

void func_reinterpret( objA* pa){

    // objB 타입의 포인터로 변환
    objB* pb = reinterpret_cast<objB*>(pa);
    pb->doSomething();
}

int main(){

    objA a;
    func_reinterpret( &a );
}

objA 객체의 구조와 상관없이 objB 객체의 포인터로 변환이 가능합니다.

심지어, 이 연산자는 long long 타입의 변수를 objB 타입의 포인터로 변환할 수도 있습니다.

int main(){

    long long n = 0;

    objB* pb = reinterpret_cast<objB*>(n);
    
    // pb포인터 사용 결과가 정의되지 않습니다.

    int ll_size = sizeof(long long);
    int objB_ptr_size = sizeof(objB*);

    cout << "long long size: " << ll_size << endl;
    cout << "objB pointer size: " << objB_ptr_size << endl;
}

▼출력

long long size: 8
objB pointer size: 8

한 마디로, 그 변수의 크기만 같다면 어떤 변환도 가능하다는 것입니다.

 

그런데, 놀라운 것은 C-style cast는 이 두 가지 기능을 모두 사용한다는 것입니다.

C-style cast는 필요하다면 다음 순서대로 형 변환을 수행합니다.

( C++ Standard, 5.4 expr.cast paragraph 5 참조 )

 

  • const_cast
  • static_cast
  • reinterpret_cast

 

실제로, 위의 const_cast와 reinterpret_cast 연산자를 사용한 예제를 C-style cast로 변경해도 똑같이 동작한다는 것을 알 수 있습니다.

 

코드가 보편적( generic )일수록 모든 경우를 고려해야 하기 때문에 구현이 점점 어려워집니다.

그런데, 단지 형 변환을 할 때마다 원하지 않는 변환이 발생할 가능성을 고려해야 한다는 것은 정말 불편한 일입니다.

"나는 static_cast로 변경을 할 수 없으면, reinterpret_cast을 호출하겠어"라고 말할 사람이 얼마나 되겠습니까.

 

그에 반해, static_cast는 명시적인 형 변환이라는 단일 목적만 수행합니다.

 

다음은, C++ 표준 라이브러리에서 std::move 함수를 구현할 때 사용하는 static_cast의 예입니다.

 template<typename _Tp>
    constexpr typename std::remove_reference<_Tp>::type&& 
    move(_Tp&& __t) noexcept
    { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

 

이 함수의 핵심은 좌측값이나 우측값을 받아서 static_cast를 사용해 우측값 참조로 변경하는 것입니다.

그래서, 이 함수를 다음과 같이 간단히 구현해 볼 수 있습니다.

class TestObj{

public:

    TestObj() = default;

    TestObj(const TestObj& other){  // 복사 생성자
        cout << "called Copy Constructor\n";
    }

    TestObj( TestObj&& other){      // 이동 생성자
        cout << "called Move constructor\n";
    }
};

int main(){

    TestObj obj;
    TestObj& rObj = obj;

    TestObj other = static_cast<TestObj&&>(rObj);	// move 연산 구현
}

▼출력

called Move constructor

위에서 좌측값을 우측값 참조로 변경해서, 이동 생성자를 호출하는 것을 볼 수 있습니다.

이 과정에서 obj의 모든 데이터가 other 객체로 이동됩니다.

 

하지만, 아래의 코드와 같이 const 참조 변수는 당연히 우측값 참조로 형 변환할 수 없습니다.

그런데, C-style cast를 사용하면, 이때도 형 변환이 가능합니다.

int main(){

    TestObj obj;
    const TestObj& crObj = obj;

    //TestObj other = static_cast<TestObj&&>(crObj);  // 형 변환 실패

    TestObj other = (TestObj&&)(crObj);    // C-style cast. very good !?
}

▼출력

called Move constructor

그리고, 이동 생성자가 호출돼서 모든 데이터가 이동됩니다.

 

std::move 함수와 이동 문법( move semantics )에 관한 내용은 여기에서 볼 수 있습니다.

 

[C++] 우측값 참조( rvalue reference )와 이동 생성자( move constructor )

우측값 참조( rvalue reference )우측값 참조란 우측값을 참조하는 데이터 타입을 말합니다.이 글에서는 우측값 참조를 왜 만들었으며, 이 참조를 사용하는 법에 대하여 설명하겠습니다. 먼저, 시간

codingembers.tistory.com

 

이 글과 관련된 글들

dynamic_cast를 통한 실시간 형 변형( type conversion )

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유