2017-11-27

[CppCoreGuidelines] const_cast는 언제 써야 하는가

 C++의 const_cast는 레퍼런싱하는 object의 cv-qualifier를 제거하는 캐스팅이다. cv-qualifier는 타입에 constness와 volatility를 더해주는 한정자이므로 const_cast는 constness뿐 아니라 volatility도 제거할 수 있다. constness를 제거하면 수정할 수 없었던 object를 수정할 수 있게 해주고, volatility를 제거하면 해당 object에 접근하는 코드가 최적화되어 사라질 수 있게 해준다. 따라서 const_cast는 실제 존재하는 object가 별도로 있고, 그것에 접근하는 방법을 변경한다.
 문제는 volatile object의 레퍼런스를 const_cast로 volatility를 없앤 뒤 이 object에 접근하는 것이나, const object의 레퍼런스를 const_cast로 constness를 없앤 뒤 이 object를 수정할 경우 이 코드가 어떻게 동작할지는 undefined behavior라는 것이다. 즉 아래와 같은 코드들은 전부 undefined behavior이다.

 그렇다면 const_cast는 언제 사용하는 것일까? 사실 const_cast를 사용해야만 하는 경우는 없다. 무언가를 하기 위해 반드시 const_cast가 필요하다고 느껴진다면 디자인에 무언가 문제가 있는 것이다. 그래서 C++ Core Guidelines에서는 const_cast를 사용하지 않는 것을 권장한다. 흔히 const_cast를 사용하는 패턴을 정리하면 아래의 3가지 패턴으로 분류할 수 있다. 이제부터 그 3가지 패턴이 왜 사용하면 안 되는지 설명할 것이다.

  첫 번째 용례는 어떤 object의 cv-qualifier를 cv1이고, 이 object를 레퍼런싱하는 cv1보다 더 cv-qualified 된 cv2를 가지는 변수를 가지고 있을 때, cv1보다 높고, cv2보다는 낮은 레벨의 cv-qualifier를 갖도록 하는 것이다. 즉, int i1이 선언돼 있고 이 i1를 지칭하는 const int& i2가 있을 때, i2를 int&로 캐스팅하는 용도다.
 하지만 잘 설계됐다면 undefined behavior를 감수하고 중간에 cv-qualified 된 레퍼런스로 들고 다닐 이유가 없다.

 두 번째는 non-const method를 가지는 클래스의 const instance가 있을 때, 이 instance를 통해 non-const method를 호출하는 것이다.
 하지만 어떤 메소드가 non-const method인 이유는 그 함수 안에서 mutable이 아닌 subobject를 수정하기 때문이다. 이를 const_cast를 통해서 강제로 호출할 경우 subobject를 수정하는 동작이 undefined behavior가 될 수 있으므로 안전하지 않다.

 마지막 사용 방법은 어떤 클래스의 const 메소드와 non-const 메소드 두 개를 정의할 때 코드의 중복을 없애기 위해서다.  이는 Scott Meyers의 Effective C++에서 추천하는 방법이기도 하고 실제로 매우 많이 보게 되는 코드다. 일반적으로 이 경우 const*로 캐스팅한 this가 반드시 non-const object를 레퍼런싱하는 것을 보장하기 때문에 안전하다. 하지만 이는 f 함수가 리턴하는 레퍼런스가 S의 멤버라는 가정하에서만이다. 예를 들어 f가 리턴하는 reference_to_r이 static storage를 가지는 const R이었다면 위의 코드는 정상적으로 컴파일되지만, non-const인 S의 instance를 통해 f를 호출할 경우 그 결과값은 const object를 지칭하는 non-const 레퍼런스이기 때문에 안전하지 않다.
 그래서 C++ Core Guidelines에서는 const_cast의 사용을 금지하고, C++11에서 추가된 decltype을 이용해서 아래와 같이 해결하는 것을 권장한다.  위의 코드에서는 f_impl의 리턴 타입이 템플릿 파라미터인 T에 의존하고, f의 결과값은 f_impl의 결과값이기 때문에, reference_to_r이 const-object의 레퍼런스라면 R& f를 컴파일할 때 에러가 발생하기 때문에 안전하게 선언할 수 있다.

 이상으로 const_cast를 사용하는 코드가 어째서 위험한지 알아보았다. 사실 C++11 이전에도 const_cast가 위험하다는 것은 널리 알려져 있었다. 하지만 코드의 중복을 제거하기 위해 const_cast를 사용하는 것을 대체할 방법이 없었기 때문에, 일반적으로 const_cast를 사용하지 말라고 할 수 없었다. 하지만 C++11에 들어간 decltypeconst_cast 없이도 코드의 중복을 없앨 방법을 제공한다. 따라서 C++11 이후로는 undefined behavior가 되기 쉬운 const_cast를 더는 쓸 이유가 없다.

댓글 2개:

  1. /* some complicate code */
    return return reference_to_r;

    이 부분 오류 날거 같은데 맞나요?

    그나저나 개발중인 프로젝트에 위의 내용이 적용될만한 부분이 있는데 좋은내용 잘 보고 갑니다.

    답글삭제