암시적 캐스팅
명시적 캐스팅
-
static_cast
- 컴파일 시점에 코드를 봐서 캐스팅이 불가능 할 것 같은 경우 컴파일 에러 발생
- 가능한 경우 캐스팅 해줌
-
const_cast
- const 변수를 일반 변수로 캐스팅, const 를 벗겨버리는 캐스팅
-
dynamic_cast(c++ 98)
- RTTI 옵션 켜져야 함.
- 캐스팅 불가능 하면 NULL 반환
- 런타임 중에 리플렉션을 이용해서 개체를 까서 캐스팅이 가능한지 판단
-
reinterpret_cast
C 스타일 캐스팅의 문제점
int score = (int)someValue;
- 개발자는 위의 코드를 무슨 의도로 넣었을까??
- C 스타일 캐스팅은 너무 무적이다. 무었이든지 허용이 된다.
- C++ 스타일의 4개의 캐스팅중 하나를 한다.
- 명백한 실수를 컴파일러가 캐치하지 못함
- C++ 캐스팅이 좀더 세분화 되어 있어서 문제를 잡는데 명확하다.
- 어셈블리 단에서는 C++과 C에서의 캐스팅은 같다.
정적 캐스팅
- 컴파일 시점에 컴파일러가 캐스팅이 가능할지 불가능할지 판단하고 불가능 하면 컴파일 에러를 발생 시킴.
- C
- C++
int number = static_cast<int>(value);
- 컴파일 중에 타입이 결정이 된다.
- 두 숫자형간의 변환
- 가능하면 값을 유지(반올림, 오차는 예외)
- 캐스팅 과정에서 값이 변할 수 있음
- float number = 3.f;
- int number2 = static_cast(number);
객체 포인터
- 변수형 체크 후 베이스 클래스를 파생 클래스로 변환
- 컴파일 시에만 형 체크 가능
House* dog = static_cast<House*>(myPet);// Compile Error
// C 스타일었다면 가능했다...
Animal* myPet = new Cat();
Cat* cat = static_cast<Cat*>(myPet);// Compile OK, 다운 캐스팅 가능
Dog* dog = static_cast<Dog*>(myPet);// Compile OK, 다운 캐스팅 가능
dog->GetDogHouseName();//런타임 에러 발생, 동적 타입은 Cat이다.
리인터프리트 캐스팅
Animal* myPet = new Cat(2, "Coco");
unsigned int address = (unsigned int)myPet;
Animal* myPet = new Cat(2, "Coco");
unsigned int address = reinterpret_cast<unsigned int>(myPet);
- reinterpret : 통역, 재해석
- C++에서 가장 위험한 casting...
- 연관없는 두 포인터간의 캐스팅 가능
char <-> House
char <-> int
- 포인터와 포인터 아닌 타입과 캐스팅 가능
char* <-> unsinged int
- '이진수 표기가 달라지지 않음'
- A형의 이진수 표기를 그냥 B형인 것처럼 해석
- 그래서 포인터와 아닌 타입간의 캐스팅이 가능 한 듯.
- static_cast vs reinterpret_cast
int* signedNum = new int(-10);
// Compile Error 유효하지 않은 값
unsigned int* unsignedNum = static_cast<unsigned int*>(signedNum);
// Compile 허용
unsigned int* unsignedNum = reinterpret_cast<unsigned int*>(signedNum);
- 리인터프리트 캐스팅은 C스타일 캐스팅과 별반 다르지 않음.
- 리인터프리트 캐스팅을 자주 사용하는 경우는 주소를 실제 숫자형으로 변할때 많이 사용 할 것 같다.
- 이진수가 달라지지 않기에 개발자가 데이터 형 변환시 좀더 확실한 예측을 할 수 있을 것 같다.
const 캐스팅
void Foo(const Animal* ptr)
{
// 'Bad Code'
Animal* animal = (Animal*)ptr;
animal->SetAge(5);
}
void Foo(const Animal* ptr)
{
// 'Bad Code'
Animal* animal = const_cast<Animal*>ptr;
animal->SetAge(5);
}
- const 를 벗겨버린다.
- 함수의 시그니처를 무시하기에 나쁜 코드이다.
- 위험한 캐스팅이라기 보다는 '하지 말아야 하는 캐스팅' 이다.
- const_cast로 형을 빠꿀수는 없음
- const 와 volatile 애트리뷰트를 제거 할 수 있음.
- 포인터 형에 사용할 때만 말이됨
- 값 형은 복사니까 const라는 것 자체가 무의미
- const_cast를 사용한다는 것 자체부터 무언가 코드를 잘못 짜고 있다고 보아도 된다.
- 그렇다면 const_cast를 사용해야 할 때는
void WriteLine(char* ptr)// 뭔가 별로인 외부 라이브러리
void MyWriteLine(const char* ptr)// 우리 프로그램에 있는 함수
{
// 외부 라이브러리를 사용하기 위해서는 어쩔 수 없이 const를 벗겨야 하는 경우
WriteLine(const_cast<char*>(ptr));
}
// 근데 위의 코드도 사실 사용되어서는 안되는 코드이다.
// 차라리 함수 제작자 한테 허락맏고 우리 함수의 const를 빼고 받는것이 좋을 것이다.
동적 캐스팅(dynamic casting)
-
C
Cat* myCat = (Cat*)myPet;
-
C++
Cat* myCat = dynamic_cast<Cat*>(myPet);
-
static 은 컴파일 중에 캐스팅... 컴파일중에 경고 발생... 그렇다면 dynamic은 실행중에 캐스팅이 된다.
-
C
Animal* myPet = new Cat();
Dog* dog = (Dog*)myPet;// compie OK
dog->GetDogHouseName();// compie 되지만 실행하면 크래시 발생
Animal* myPet = new Cat();
Dog* dog = dynamic_cast<Dog*>(myPet);// compie OK, return NULL
if(dog)<--NULL이 아닐때만...
{
dog->GetDogHouseName();
}
- 실행중에 캐스팅을 한다.
- 호환되지 않는 자식형으로 캐스팅 할때는 NULL을 반환한다.
- 포인터 또는 참조형을 캐스팅 할 때만 사용 가능.
- "But 이걸 사용하려면 RTTI(Real time type information)이라는 컴파일 옵션을 켜야함".
- C++ 프로젝트에서는 기본적으로 끈다. 익셉션과 마찬가지로
- 왜 끌까? 이유는 성능, C++은 성능때문에 사용하는데 이 RTTI를 킬만큼 성능 과부하를 견딜 수 가 없음.
- dynamic_cast, static_cast는 실제로는 동일하게 동작, 그러나 RTTI가 켜져 있으면 다름.
- 정말 RTTI를 사용하고 싶다면 모든 클래스 타입이 아닌 일부 클래스 타입에 대해서만 사용하고 싶을 것이다. 그럴경우 직접 클래스 안에 타입 ID정보를 나타낼 수 있는 멤버를 추가하여 직접 구현한다.
C++ 의 베스트 프랙티스
- 규칙 : 제일 안전한 것 부터 시작해서 조금씩 위험한 것으로 빠진다.
- 기본적으로는 static_cast를 사용하자
- static_cast로 힘들면 reinterpret_cast를 사용
- 포인터와 비포인터 사이 변환
- 서로 연관이 없는 포인터 사이의 변환은 그 데이터 형이 '정말 확신'이 들었을 때만 사용
- 내게 변경 권한이 없는 외부 라이브러리를 호출할 때만 const_cast를 사용
- 이렇게 사용하면 static_cast만 보다가 몇몇개의 reinterpret_cast 가 나오면 주의깊게 보기에 코드 검사 하는 입장에서도 보기 좋다.