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*냐의 차이죠. ((const 관련 내용이 얼마전 시즈하님의 C 포인터, 확실히 알자(6) - 상수와 포인터에도 올라왔었습니다.))

무엇의 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(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까지 필요할까라는 생각이 드네요. :-)

Comments

Popular Posts