KoreanFoodie's Study
C++ DevNote : const_cast 의 개념과 주의점 본문
C++ 에 대해 공부한 것과, 개발하면서 알게 된 것들을 다룹니다
const_cast
많은 사람들이 알고 있듯이, C++ 에서 제공하는 casting 의 종류는 크게 4 가지이다. 각 용도를 간단히 적어보면 다음과 같다 :
- static_cast : '일반적인' C 스타일의 캐스팅. 보통 업캐스팅(파생 클래스의 포인터를 기반 클래스의 포인터로 가리킴) 시 사용한다.
- dynamic_cast : 상속 관계에 있는 클래스들 사이의 캐스팅. 보통 다운 캐스팅(기반 클래스의 포인터를 파생 클래스의 포인터로 가리킴) 시 사용한다. 만약 실패하면 nullptr 를 리턴한다. 참고로, dynamic_cast 는 polymorphic 한 클래스만 가능한데, 간단히 말하면 virtual 함수가 포함된 클래스이어야 dynamic_cast 가 작동한다. virtual 함수가 없는 클래스를 dynamic_cast 하려고 시도하면 컴파일 에러가 발생한다(bad_cast 를 throw함).
- reinterpret_cast : 관련이 없는 포인터들 사이의 변환.
- const_cast : 객체의 상수성(constness)를 없앨 때 사용하는 캐스팅.
이 중 const_cast 는 사실 비용이 비싸고 주의할 점이 조금 존재하는 녀석이다. 다음 예제 코드를 보자. 출처는 stackoverflow의 Should I use const_cast? 이다.
const bool do_it = true;
bool *ptr = const_cast<bool*>(&do_it);
*ptr = false;
// optimizer probably thinks this is always true
if (do_it) initiate_nuclear_first_strike();
위 코드에서, *ptr 가 do_it 의 값을 바꾸는 부분이 있지만, 컴파일러는 do_it 이 const 로 선언되었으므로 if 문에서의 조건이 항상 true 라고 가정해 버린다. 이와 같은 이슈는 심각한 문제를 발생시킬 수 있다.
const_cast 를 사용할 때는, 먼저 const_cast 가 정말로 반드시 필요한지를 다시 한 번 고민해 보아야 한다. 일반적으로 const_cast 를 사용하는 경우는 const 로 타입이 잡혀 있는 레거시 코드를 사용할 때, const 를 해제해야 하는 경우 정도이다.
그렇다면 본격적으로 const_cast 를 어떻게 사용해야 하는지 알아보자 : (자세한 내용은 cppreference 의 설명 참고. 각 예제 코드는 이 블로그를 참고함)
1. const_cast 는 포인터 간에 사용해야 한다.
const int i = 3;
int j = const_cast<int>(j); // error!
2. const 로 선언된 변수에 대해서 const_cast 를 사용하면 안된다. const_cast 를 사용할 때는 변수에 대해 포인터를 새로 만들고 사용한다.
const int i = 3; // i 는 const 로 선언
int* pi = const_cast<int*>(&i);
*pi = 4; // undefined behavior
이게 왜 안될까? *pi = 4; 를 통해 i 를 바꿀 수 있을 것처럼 보이지만... &i 와 pi 는 같은 주소값이 나오지만, 실제로 i 와 *pi 의 값을 찍어보면 각각 3, 4 가 나온다(같은 주소, 다른 값)
3. const 로 선언된 변수는 수정할 수 없다(당연하다).
4. const_cast 사용 시, const 로 선언되지 않은 변수를 가리키는 포인터/레퍼런스에 대해 포인터 간 const_cast 를 사용하여 불변성을 제거한다.
// 레퍼런스 예시
int i = 3; // i is not declared const
const int& rci = i;
const_cast<int&>(rci) = 4; // OK: modifies i
// 예시 종합 + 포인터
struct myType
{
int i;
myType(int i) : i(i){};
void foo()
{
i++;
}
void bar() const
{
const_cast<myType *>(this)->i++;
}
};
const myType* t4 = new myType(0);
// t4->foo(); // error;
t4->bar();
myType* ptr_t4 = const_cast<myType*>(t4);
ptr_t4->foo();
ptr_t4->bar();
std::cout << t4->i << '\n';