스토리텔링 개발자

[More Effective C++] 2. C++ 스타일 캐스팅 본문

개발/More Effective C++

[More Effective C++] 2. C++ 스타일 캐스팅

김디트 2024. 8. 2. 10:58
728x90

항목 2 : 가능한 C++ 스타일의 캐스트를 즐겨 쓰자.

 

 

 

C 스타일 캐스트의 문제점
  1. 타입을 다른 타입으로 제한 없이 바꾸어준다.
    • 어떤 객체의 상수성(copnstness)만을 바꾼다.
    • 기본 클래스 객체에 대한 포인터를 파생 클래스 객체에 대한 포인터로 바꾼다.
    • 위 둘은 전혀 다른 캐스트지만, C 스타일 캐스트는 이 모든 걸 별 말 없이 소화해버리기 때문에 위험하다.
  2. C 스타일 캐스트는 눈으로 찾아내기가 힘들다.
    • 식별자를 괄호로 둘러싼 것일 뿐이기 때문이다.
int firstNumber, secondNumber;
...
// c 스타일 캐스트
double result = ((double)firstNumber) / secondNumber;
// c++ 스타일 캐스트
// 인간의 눈이나 프로그램이나 발견하기 쉬운 캐스트
double result2 = static_cast<double>(firstNumber) / secondNumber;

 

 

 

C++ 스타일 캐스트의 종류
  • static_cast
    • C 스타일 캐스트와 같은 의미와 형변환 능력을 가지고 있는, 기본적인 캐스트 연산자.
    • 제약도 C 스타일 캐스트와 동일하다.
      • struct -> int 캐스트, double -> 포인터 캐스트 등은 불가능하다.
    • 상수성 제거도 불가능하다.
  • const_cast
    • 표현식의 상수성(constness)이나 휘발성(volatileness)을 없애는 데 사용한다.
    • const나 volatile 특성을 바꾸고 싶을 때'만' 사용한다.
class Widget { ... };
class SpecialWidget : public Widget { ... };

void update(SpecialWidget* psw);

SpecialWidget sw;
const SpecialWidget& csw = sw; // 상수 객체로 참조

// 실패 코드
update(&csw); // 상수 객체는 넣을 수 없으므로 컴파일 에러!

// 성공 코드
update(const_cast<SpecialWidget*>(&csw)); // const 제거
update((SpecialWidget*)&csw); // C 캐스트 스타일로 const 제거

Widget* pw = new SpecialWidget;

// 실패 코드
update(pw); // Widget* 을 넣으려 하므로 컴파일 에러!
update(const_cast<SpecialWidget*>(pw)); // const_cast는 상수성만 제거 가능하므로 컴파일 에러!
  • dynamic_cast
    • 상속 계층 관계를 가로지르거나 하향시킨 클래스 타입으로 안전하게 캐스팅한다.
    • 기본 클래스 포인터, 참조자 타입 -> 파생, 형제 클래스 포인터, 참조자 타입
    • 실패 시
      • 포인터 캐스팅의 경우 널 포인터 리턴
      • 참조자 캐스팅의 경우 예외 발생
    • 제약
      • 상속 계층 구조를 오갈 때만 가능하다.
      • 가상함수가 없는 타입에는 적용이 불가하다.(항목 24 참조)
      • 상수성 제거가 불가능하다.
Widget* pw;

// 포인터 다운 캐스팅
update(dynamic_cast<SpecialWidget*>(pw));
// 실패 시 null 포인터 리턴

void updateViaRef(SpecialWidget& rsw);

// 참조자 다운 캐스팅
updateViaRef(dynamic_cast<SpecialWidget&>(*pw));
// 실패 시 예외 발생


// 제약으로 인한 캐스팅 실패
int firstNumber, secondNumber;
double result = dynamic_cast<double>(firstNumber) / secondNumber; // 컴파일 오류!!
// 가상 함수가 전혀 없다.

const SpecialWidget sw;
update(dynamic_cast<SpecialWidget*>(&sw)); // 컴파일 오류!!
// 상수성을 제거하는 데 사용하려 했다.
  • reinterpret_cast
    • 변환 결과가 거의 항상 컴파일러에 따라 다르게 정의되어 있다.
      • 직접 이식이 불가능하다.
    • 보통 함수 포인터 타입 변환에 사용한다.
      • 하지만 이는 소스의 이식성을 저하시킨다.
      • 어떤 경우 이런 캐스팅은 잘못된 결과를 도출한다.(항목 31 참조)
      • 그러므로 피하도록 하자.
typedef void (*FuncPtr)(); // 함수 포인터
FuncPtr funcPtrArray[10]; // 함수 포인터에 대한 배열

int doSomething(); // 이를 funcPtrArray에 넣고자 하면..

// 실패 코드
funcPtrArray[0] = &doSomething; // 타입 불일치로 컴파일 에러
// 성공 코드
funcPtrArray[0] = reinterpret_cast<FuncPtr>(&doSomething); // 컴파일 성공

 

 

 

참조
 

[Effective C++] 27. 캐스팅

항목 27 : 캐스팅은 절약, 또 절약! 잊지 말자   C++의 동작 규칙어떤 일이 있어도 타입 에러가 생기지 않도록 보장한다.즉, 이론적으로는 컴파일만 깔끔하게 끝나면 그 이후엔 어떤 객체에 대해

delightlane.tistory.com

 

728x90
Comments