Notice
Recent Posts
Recent Comments
«   2024/12   »
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
Archives
Today
Total
관리 메뉴

SYDev

C++ Chapter 16-1 : C++에서의 형 변환 연산 본문

Programming Lang/C++

C++ Chapter 16-1 : C++에서의 형 변환 연산

시데브 2023. 8. 1. 19:58

C 스타일 형 변환 연산자

#include <iostream>
using namespace std;

class Car
{
private:
    int fuelGuage;
public:
    Car(int fuel) : fuelGuage(fuel)
    { }
    void ShowCarState() { cout<<"잔여 연료량: "<<fuelGuage<<endl; }
};

class Truck : public Car
{
private:
    int freightWeight;
public:
    Truck(int fuel, int weight) : Car(fuel), freightWeight(weight)
    { }
    void ShowTruckState()
    {
        ShowCarState();
        cout<<"화물의 무게: "<<freightWeight<<endl;
    }
};

int main(void)
{
    Car * pcar1=new Truck(80, 200);
    Truck * ptruck1=(Truck *)pcar1; //기초 클래스의 포인터 형을 유도 클래스의 포인터 형으로 형 변환하는 것은 일반적이지 않음
    ptruck1 ->ShowTruckState();
    cout<<endl;

    Car * pcar2=new Car(120);
    Truck * ptruck2=(Truck *)pcar2; //포인터가 가리키는 객체가 Car 객체이므로, 문제가 있는 문장
    ptruck2 ->ShowTruckState();
    
    return 0;
}
잔여 연료량: 80
화물의 무게: 200

잔여 연료량: 120
화물의 무게: 0
  • 위 예제와 같이 문제가 있는 형 변환문도 C 스타일 형 변환 연산으로 형 변환을 진행해버리면 컴파일 오류 없이 컴파일이 진행되어 버린다.
  • C++에서는 총 4개의 연산자를 추가로 제공하면서 용도에 맞는 형 변환 연산자의 사용을 유도한다.

 

dynamic_cast: 상속관계에서의 안전한 형 변환

dynamic_cast<T>(expr)
  • <>사이에 변환하고자 하는 자료형의 이름(객체의 포인터 또는 참조형)
  • ()사이에 변환의 대상

 

 dynamic_cast 형 변환 연산자는 상속관계에 놓여 있는 두 클래스 사이에서 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우 사용한다.

#include <iostream>
using namespace std;

class Car
{
private:
    int fuelGuage;
public:
    Car(int fuel) : fuelGuage(fuel)
    { }
    void ShowCarState() { cout<<"잔여 연료량: "<<fuelGuage<<endl; }
};

class Truck : public Car
{
private:
    int freightWeight;
public:
    Truck(int fuel, int weight) : Car(fuel), freightWeight(weight)
    { }
    void ShowTruckState()
    {
        ShowCarState();
        cout<<"화물의 무게: "<<freightWeight<<endl;
    }
};

int main(void)
{
    /*
    Car * pcar1=new Truck(80, 200);
    Truck * ptruck1=dynamic_cast<Truck*>(pcar1);    //compile error

    Car * pcar2=new Car(120);
    Truck * ptruck2=dynamic_cast<Truck*>(pcar2);    //compile error
    */

    Truck * ptruck3=new Truck(70, 150);
    Car * pcar3=dynamic_cast<Car*>(ptruck3);    //compile ok

    return 0;
}

 

static_cast: A 타입에서 B 타입으로

static_cast<T>(expr)

 

 static_cast 형 변환 연산자는 유도 클래스의 포인터 및 참조형 데이터를 기초 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우 뿐만 아니라, 기초 클래스의 포인터 및 참조형 데이터를 유도 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우도 허용한다.

#include <iostream>
using namespace std;

class Car
{
private:
    int fuelGuage;
public:
    Car(int fuel) : fuelGuage(fuel)
    { }
    void ShowCarState() { cout<<"잔여 연료량: "<<fuelGuage<<endl; }
};

class Truck : public Car
{
private:
    int freightWeight;
public:
    Truck(int fuel, int weight) : Car(fuel), freightWeight(weight)
    { }
    void ShowTruckState()
    {
        ShowCarState();
        cout<<"화물의 무게: "<<freightWeight<<endl;
    }
};

int main(void)
{
    Car * pcar1=new Truck(80, 200);
    Truck * ptruck1=static_cast<Truck*>(pcar1);    //compile ok
    ptruck1->ShowTruckState();
    cout<<endl;

    Car * pcar2=new Car(120);
    Truck * ptruck2=static_cast<Truck*>(pcar2);    //compile ok! but..
    ptruck2->ShowTruckState();

    return 0;
}
잔여 연료량: 80
화물의 무게: 200

잔여 연료량: 120
화물의 무게: 0

 static_cast 형 변환 연산자는 보다 많은 형 변환을 허용하는 대신에, 위 예제와 같이 잘못된 상황에서의 형 변환도 허용하기 때문에 제한적인 상황에서 책임질 수 있는 경우에만 사용하는 것이 좋다.

 

 static_cast 형 변환 연산자는 다음과 같이 기본 자료형 간의 형 변환도 가능

double result=static_cast<double>(20)/3;

 

static_cast와 C 스타일 형 변환 연산자의 차이점

  • static_cast 연산자는 C 스타일 형 변환 연산자와 달리, '기본 자료형 간의 형 변환''클래스의 상속관계에서의 형 변환'만 허용
  • C 스타일 형 변환 연산자는 다음과 같은 일반적이지 않은 형 변환도 허용한다.

 

int main(void)
{
    const int num=20;
    int * ptr=(int*)&num;   //const 상수의 포인터는 const 포인터
    *ptr=30;    //const 상수 num의 값이 실제로 변경됨
    cout<<*ptr<<endl;   //30이 출력

    float * adr=(float*)ptr;    //int형 포인터를 float형으로 변환
    cout<<*adr<<endl;   //저장된 데이터를 float형으로 해석해서 출력
}

 

const_cast: const의 성향 삭제

const_cast<T>(expr)

 

 const_cast 형 변환 연산자는 포인터와 참조자의 const 성향을 제거하는 형 변환을 목적으로 하는 연산자이다.

 

#include <iostream>
using namespace std;

void ShowString(char* str)
{
    cout<<str<<endl;
}

void ShowAddResult(int& n1, int& n2)
{
    cout<<n1+n2<<endl;
}

int main(void)
{
    const char * name="Lee Sung Ju";
    ShowString(const_cast<char*>(name));    //const char*형 데이터를 char*형으로 형 변환

    const int& num1=100;
    const int& num2=200;
    ShowAddResult(const_cast<int&>(num1), const_cast<int&>(num2));  //const int&형 데이터를 int&형으로 형 변환

    return 0;
}
Lee Sung Ju
300

 

reinterpret_cast: 상관없는 자료형으로의 형 변환

const_cast<T>(expr)

 

 reinterpret_cast 형 변환 연산자는 포인터를 대상으로 하는, 그리고 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다.

 

#include <iostream>
using namespace std;

int main(void)
{
    int num=0x010203;
    char * ptr=reinterpret_cast<char*>(&num);   //int형 정수에 바이트 단위 접근을 위해 int형 포인터를 char형 포인터로 형 변환

    for(int i=0; i<sizeof(num); i++)
        cout<<static_cast<int>(*(ptr+i))<<endl; //바이트 단위 데이터를 문자가 아닌 정수의 형태로 출력하기 위해 char형 데이터를 int형으로 변환

    return 0;
}
3
2
1
0

+ 위 예제는 원래는 4바이트 크기인 int를 char로 바꿔서 1바이트씩 쪼개서 사용하는 예제라고는 하는데 아직은 잘 모르겠다.. 아무튼 reinterpert_cast 형 변환이 포인터를 대상으로 하는 형 변환이라는 것에 집중하자.

 

 reinterpret_cast는 다음과 같은 형 변환도 가능하다.

int main(void)
{
    int num=72;
    int* ptr=&num;

    int adr=reinterpret_cast<int>(ptr); //주소 값을 정수로 변환
    cout<<"Addr: "<<adr<<endl;  //주소 값 출력

    int* rptr=reinterpret_cast<int*>(adr);  //정수를 다시 주소 값으로 변환
    cout<<"value: "<<*rptr<<endl;   //주소 값에 저장된 정수 출력
    . . . . 
}

 

dynamic_cast 두 번재 이야기: Polymorphic 클래스 기반의 형 변환

  • 기초 클래스가 Polymorphic 클래스인 경우에 한하여, dynamic_cast 연산자도 기초 클래스의 포인터 및 참조형 데이터를 유도 클래스의 포인터 및 참조형 데이터로의 형 변환을 허용한다.
  • Polymorphic 클래스: 하나 이상의 가상함수를 지니는 클래스

 이와 관련된 다음 예제를 살펴보자.

#include <iostream>
using namespace std;

class SoSimple  //Polymorphic 클래스
{
public:
    virtual void ShowSimpleInfo()
    {
        cout<<"SoSimple Base Class"<<endl;
    }
};

class SoComplex : public SoSimple
{
public:
    void ShowSimpleInfo()   //이것 역시 가상함수
    {
        cout<<"SoComplex Derived Class"<<endl;
    }
};

int main(void)
{
    SoSimple * simPtr=new SoComplex;	//simPtr이 SoComplex 객체를 가리키는 포인터이며, SoComplex가 comPtr이 가리켜도 되는 객체이기 때문에 형 변환이 가능 
    SoComplex * comPtr=dynamic_cast<SoComplex*>(simPtr);    //기초 클래스인 SoSimple형 포인터 변수 simPtr을 유도 클래스인 SoComplex형 포인터로 형 변환
    comPtr->ShowSimpleInfo();
    
    return 0;
}
SoComplex Derived Class

 위 상황이 가능해지는 이유는 main함수를 살펴보면 알 수 있다. 만약에 simPtr이 SoSimple 객체를 가리키는 포인터 변수였다면, 형 변환의 결과로 NULL 포인터가 반환된다. 

 

 이와 관련하여 위 예제에서 main 함수만 다음과 같이 바꾸면 출력되는 값이 바뀐다.

int main(void)
{
    SoSimple * simPtr=new SoSimple;
    SoComplex * comPtr=dynamic_cast<SoComplex*>(simPtr);    
    if(comPtr==NULL)
        cout<<"형 변환 실패"<<endl;
    else    
        comPtr->ShowSimpleInfo();
    
    return 0;
}
형 변환 실패
  • 결론적으로, dynamic_cast 안정적인 형 변환을 보장하지만, 컴파일 시간이 아닌 프로그램이 실행중인 동안에 안전성을 검사한다. 그렇기 때문에, static_cast에 비해 실행 속도가 느리다.
  • 반면에, static_cast실행시간에 안전성 검사를 별도로 진행하지 않는다. 그렇기 때문에, dynamic_cast에 비해 실행 속도가 빠르다.

 

bad_cast 예외

  • bad_cast 예외는 dynamic 연산자를 이용한 형 변환의 과정에서 발생할 수 있는 예외이다.
#include <iostream>
using namespace std;

class SoSimple
{
public:
    virtual void ShowSimpleInfo()
    {
        cout<<"SoSimple Base Class"<<endl;
    }
};

class SoComplex : public SoSimple
{
public:
    void ShowSimpleInfo()
    {
        cout<<"SoComplex Derived Class"<<endl;
    }
};

int main(void)
{
    SoSimple simObj;
    SoSimple& ref=simObj;

    try
    {
        SoComplex& comRef=dynamic_cast<SoComplex&>(ref);
        /*
         ref가 참조하는 대상이 SoSimple 객체이기 때문에 
        SoComplex 참조형으로의 형 변환은 안전하지 못하다.
         또한, 참조자를 대상으로는 NULL을 반환할 수 없기 때문에
        이런 상황에서는 bad_cast 예외가 발생한다.
        */
        comRef.ShowSimpleInfo();	//예외의 발생으로 실행되지 못한다.
    }
    catch(bad_cast expt)
    {
        cout<<expt.what()<<endl;	//what 함수는 예외의 원인을 문자열의 형태로 반환한다.
    }
    
    return 0;
}
std::bad_cast

 


참고자료

  • 윤성우, <윤성우의 열혈 C++ 프로그래밍>, 오렌지미디어, 2010.05.12