[C++] 누구나 쉽게, 리팩토링(클린코드)-③ 캐스팅/형변환 종류 및 방법

리팩토링

 

리팩토링은 내부의 코드를 개선하는 하나의 방법론이자 솔루션을 뜻합니다. 

 

리팩토링을 한마디로 말하자면, 나쁜 냄새가 나는 코드를 최적화하는 것입니다. 여기서 주의할 점은 런타임 시의 최적화가 아니라 프로그래밍을 할 때 버그 없는 프로그램을 만드는 것입니다외부 인터페이스는 그대로 두고 내부의 코드만을 개선하여 재사용과 가독성을 높이는 데 목적이 있습니다.

 

 

 

 

이번시간에는 리팩토링을 진행하면서 캐스팅 개념에 대해 살펴보겠습니다.

 


3. 캐스팅

캐스팅이란 형변환을 뜻합니다.  잘못된 형변환은 오류를 불러올 수 있습니다.

 

1) char* pa = malloc(sizeof(char));  

2) char* pb = (char*)malloc(sizeof(char));

 

위 두 문장은 C 및 C++ 환경에서 문제없이 동작할까요? 정답은 아래에서 공개합니다!

더보기

1) C에서는 잘 동작하며 C++에서는 타입에러! 발생!

2) C/C++에서 모두 잘 동작합니다

 

타입체크 기능

C언어 :  타입체크 기능 X,  C++ : 타입체크 강화(에러발생)

C언어에서는 명시적으로 타입변환을 하지 않아도 컴파일러는 알아서 잘 짰거니 하고 개발자가 믿고 실행합니다. 논리적으로 잘못됐어도 에러를 내지 않으니 잘못 개발한 개발자는 월화수목금금금...😭 이와 달리, C++에서는 타입 체크가 강화되어 타입을 명시하지 않으면 컴파일 에러가 납니다. 따라서 C++에서는 타입을 정확히 명시해주어야합니다.

 

그러나(char*) 이런 식으로 명시적 캐스팅 연산자를 쓰는 건 냄새나는 코드입니다. 이렇게 명시를 통한 캐스팅은 컴파일러의 타입체크 기능을 꺼버리기 때문입니다.

 

♥ 컴파일러가 형변환시 에러를 체크하게끔 하라!! 

하여 4가지 캐스팅에 대해 살펴보겠습니다.

 

 

 

1) static_cast  

 

앞서 말했듯이 C++은 명시적인 형변환 연산자를 사용하면 안 됩니다. 이는 컴파일러의 타입 체크 기능을 끄게 하기 때문입니다. 대신 static_cast를 사용하여 이상적으로 형 변환을 합니다.

 

char *pc = static_cast<char*>(malloc(sizeof(char)));

 

static_cast는 인스턴스 되기 전에 컴파일 타임에 캐스팅됩니다. 컴파일러가 캐스팅을 책임지므로 아름답죠

 

 

 

2) reinterpret_cast  

 

int x  =  0x1;

char *p  = &x;

 

x는 int형인데 char형 포인터를 사용할 경우(unreasonable error라고 표현합니다) C언어에선 문제없었지만, C++에서는  강력한 타입 체크 기능에  걸려(포인터의 타입이 다르기 때문에 )에러가 납니다. 

 

빅엔디안인지 리틀 엔디안인지 확인해 보는 경우와 같이 의도적으로 비이상적인 코딩을 한 경우에도 C++에서는 에러를 내어 개발자가 의도적으로 사용하고 싶어도 쓰지 못하게 됩니다.  이는 컴파일러가 비이상적이라고 생각했기 때문입니다.

 

char *p = reinterpret_cast<char*>(&x);

 

이러한 경우에  의도적으로 비이상적인 형 변환을 하고자 한다면, reinterpret_cast 사용하면 됩니다. reinterpret_cast는 비이상적 형 변환을 지원합니다. 

 

 

3) const_cast

const double PI = 3.14;

double* p = &PI;

 

const 상수를 사용하였으므로 논리적으로 변환이 되지 않도록 하는 것이 맞지만 그럼에도 불구하고 변환을 해야 할 때가 있습니다.  이럴 때 논리적 상수성을 깨려면 const_cast를 사용합니다.

 

double* p = const_cast<double*>(&PI);

 

 

** const_cast 캐스트를 한다는 건 const 상수 설계가 제대로 되지 않은 경우가 많습니다. 그러므로 가급적이면 안쓰는 게 좋습니다.

 

 

 

4) dynamic_cast

이번엔 1탄에서 사용한 Animal 클래스를 살펴봅시다. 간단하게 Animal 클래스를 설명하면, Animal 클래스를 상속받은 클래스들은 동물들이 우는 기능(cry())을 강제화하게끔 추상클래스로 구현하였고 Dog클래스는 Animal클래스의 자식클래스입니다.

 

 

업캐스팅Upcasting

22 라인의 Animal* p = &d; 는 Animal과 Dog의 타입이 달라 statc_cast 캐스팅하여 Animal *p = static_cast<Animal*>(&d); 로 써야하는 것 아닌가? 하겠지만,

 

♥ 업캐스팅시 상속 관계에 있을 때는 캐스팅 연산자 생략 가능합니다. 컴파일 시에 이미 두 클래스 간의 관계를 알고 있기 때문입니다. 따라서 static_cast를 쓰지 않아도 Animal* p = &d;로 표현 가능한 것이죠.

 

 

다운캐스팅Downcasting

그렇다면 다운 캐스팅은 어떠할까요? Animal을 Dog로 다운 캐스팅하는 Dog* pDog = p; 는 불가능합니다.

 

♥  Downcasting에서는 연산자를 생략할 수 없습니다. 부모의 타입을 자신의 타입으로, 안전한 다운 캐스팅을 하고 싶다면 dynamic_cast 연산자를 사용해야 합니다.

 

Dog* pDog = dynamic_cast<Dog*>(p);

 

이때 주의해야할 점은 dynamic_cast는 컴파일에 체크되지 않으므로 26~29라인처럼 런타임에 체크하게 합니다.

if( pDog != 0)
    cout << "Dog" << endl;
else
    cout << "!Dog" << endl;

 

 

정리

// 1) static_cast : 보편적인 캐스팅 시(명시적 형변환 안돼요)
char *pc = static_cast<char*>(malloc(sizeof(char)));


// 2) reinterpret_cast : 비이상적 캐스팅 시
int x = 0x1; 
char *p = &x;

char *p = reinterpret_cast<char*>(&x);


// 3) const_cast : const 캐스팅
const double PI = 3.14;
double* p = &PI;

double* p = const_cast<double*>(&PI);



// 4) dynamic_cast : 다운캐스팅 시 사용
// Animal 클래스는 Dog 클래스의 부모 클래스일 때,

Dog d;
Animal* p = &d;

Dog* pDog = dynamic_cast<Dog*>(p);

 1)static_cast : 이상적 캐스팅에 사용
 2) reinterpret_cast : 비이상적 캐스팅에 사용
 3) const_cast : 상수 변환에 사용
 4) dynamic_cast : 다운캐스팅에 사용

 

 

 

이상 캐스트에 대하여 살펴보았습니다.

반응형
그리드형

댓글

❤️김세인트가 사랑으로 키웁니다❤️