C++ of the Day #41 – Maintaining const in Impl classes
이번 내용은 c.l.c.m.에 올라온 Maintaining const in Impl classes라는 글에서 가지고 왔습니다. Pimpl Idiom과 const correctness에 관한 글입니다.
struct Foo { struct Impl { void A() const { }; // unreachable code! void A() { }; }; Foo() : pimpl_(new Impl) { }; void A() const { pimpl_->A(); }; void A() { pimpl_->A(); }; Impl* pimpl_; }; // ... Foo const foo; foo.A(); // will always call non-const Foo::Impl::A()
위의 코드에서 문제라고 얘기되고 있는 것은 foo가 Foo 클래스의 const instance인데 Impl의 A() const가 호출되지 않고 A()가 호출된다는 점입니다.
원인은 간단합니다. foo가 const instance라서 const가 되는 것은 Impl*이지 Impl이 아니기 때문입니다. Impl* const냐 Impl const*냐의 차이죠.1
무엇의 const인지 쉽게 알기 위해서는 const 키워드의 바로 앞에 무엇이 있는지 보면 됩니다. 예를 들어보죠.
- T const* -> T가 const
- T* const -> T*가 const
- std::auto_ptr<T const> -> T가 const
- std::auto_ptr<T> const -> std::auto_ptr<T>가 const
- boost::shared_ptr<T const> -> T가 const
- boost::shared_ptr<T> const -> boost::shared_ptr<T>가 const
위 공식(?)에 맞추기 위해 const T* 대신 T const* 라고 썼습니다. 게다가 몇 년전부터 T const* 형식이 유행인 듯... 왠지 멋져 보입니다.
따라서 위의 문제에서 Impl::A() const 함수가 불리게 하기 위한 가장 간단한 방법은 Foo::A() const를 다음과 같이 수정하는 것입니다.
void Foo::A() const { const_cast<Impl const*>(pimpl_)->A(); }
만약 다뤄야 할 멤버 함수가 많다면 다음과 같이 get_impl()같은 helper 함수를 만들어 사용하는 것이 좋겠죠?
struct Foo { struct Impl { void A() const { }; void A() { }; }; Impl* get_pimpl() { return pimpl_; } Impl const* get_pimpl() const { return pimpl_; } Foo() : pimpl_(new Impl) { }; void A() const { get_pimpl()->A(); }; void A() { get_pimpl()->A(); }; Impl* pimpl_; };
원본 링크의 김승범님 답변을 보면 이 용도의 PimplPtr라는 smart pointer 클래스도 볼 수 있습니다. 이 PimplPtr는 일반 smart pointer와는 다르게 T* 대신 T const*를 리턴하도록 구현되어 있죠.
개인적으론 Impl 클래스는 말 그대로 원래 클래스의 private에 해당하는 코드로 그 클래스에서만 사용하는 코드들인데 굳이 const correctness까지 필요할까라는 생각이 드네요.
- const 관련 내용이 얼마전 시즈하님의 C 포인터, 확실히 알자(6) - 상수와 포인터에도 올라왔었습니다.[back]


