4. Compiler Firewalls and the Pimpl Idiom

26. Minimizing Compile-time Dependencies-Part 1

아래 코드에서 어떤 #include를 삭제 가능한가?

//  x.h: original header 
//
#include <iostream>
#include <ostream>
#include <list>
// None of A, B, C, D or E are templates.
// Only A and C have virtual functions.
#include "a.h"  // class A
#include "b.h"  // class B
#include "c.h"  // class C
#include "d.h"  // class D
#include "e.h"  // class E
class X : public A, private B
{
public:
     X( const C& );
  B  f( int, char* );
  C  f( int, C );
  C& g( B );
  E  h( E );
  virtual std::ostream& print( std::ostream& ) const;
private:
  std::list<C> clist_;
  D            d_;
};
inline std::ostream& operator<<( std::ostream& os, const X& x )
{
  return x.print(os);
}

iostream을 삭제하라. 불필요한 헤더를 포함하지 말라. ostream을 iosfwd로 대체하라. 스트림의 전방 선언으로 충분할 때에는 iosfwd를 사용하라. e.h 대신 class E로 대체하라. 전방 선언으로 충분할 경우에는 헤더를 포함하지 말라.

27. Minimizing Compile-time Dependencies-Part 2

또 어떤 헤더를 삭제 가능할까?

pimpl 관용구를 쓰고 a.h, b.h, list, c.h, d.h는 삭제하자. 많이 쓰이는 클래스에 대해서는 컴파일러 방화벽 관용구(pimpl 이디엄)을 사용해 세부 구현을 숨기는 것을 선호하라. private 멤버들을 불명확 포인터에 담으라.

28. Minimizing Compile-time Dependencies-Part 3

여기서 더 분리가 가능할까?

//  x.h: after converting to use a Pimpl 
//       to hide implementation details
//

#include <iosfwd>
#include <memory>
#include "a.h"  // class A (has virtual functions)
#include "b.h"  // class B (has no virtual functions)
class C;
class E;

class X : public A, private B
{
public:
     X( const C& );
  B  f( int, char* );
  C  f( int, C );
  C& g( B );
  E  h( E );
  virtual std::ostream& print( std::ostream& ) const;
private:
  struct XImpl;
  std::unique_ptr<XImpl> pimpl_;
    // opaque pointer to forward-declared class
};

inline std::ostream& operator<<( std::ostream& os, const X& x )
{
  return x.print(os);
}

상속을 컴포지션으로 바꾸자. 컴포지션으로 충분할 때에는 상속을 쓰지 말라.

//  x.h: after removing unnecessary inheritance 
//
#include <iosfwd>
#include <memory>
#include "a.h"  // class A
class B;
class C;
class E;
class X : public A
{
public:
     X( const C& );
  B  f( int, char* );
  C  f( int, C );
  C& g( B );
  E  h( E );
  virtual std::ostream& print( std::ostream& ) const;
private:
  struct XImpl;
  std::unique_ptr<XImpl> pimpl_; // this now quietly includes a B
};
inline std::ostream& operator<<( std::ostream& os, const X& x )
{
  return x.print(os);
}

29. Compilation Firewalls

pimpl_ 오브젝트에는 무엇이 와야 할까? 두 가지 선택지가 있다. 1. 비가상 private 멤버를 전부 넣는다. 2. 제한된 클래스의 경우에는 XImpl을 X가 되었어야 했던 것으로 쓰고 X는 포워딩만 수행하는 public 인터페이스로 쓴다. 많이 쓰이는 클래스에 대해서는 컴파일러 방화벽 관용구(pimpl 이디엄)을 사용해 세부 구현을 숨기는 것을 선호하라. private 멤버들을 불명확 포인터에 담으라. XImpl은 X 오브젝트에 대한 포인터를 담아야 하는가? 가끔은 그럴 수도 있다.

30. The “Fast Pimpl” Idiom

Pimpl 관용구의 공간/성능 복잡도는 어떻게 될까? 공간 오버헤드는 대개 포인터 1개 + 정렬 공간 오버헤드 만큼이다. 시간 오버헤드는 포인터 할당/해제와 하나 이상의 간접 접근으로 인한 오버헤드가 든다. 성능 프로파일링에서 필요가 생기지 않는다면 인라이닝이나 성능 조정을 하지 말라.

// evil dastardly header file x.h 
class X
{
  /* . . . */
  static const size_t sizeofximpl = /*some value*/;
  char pimpl_[sizeofximpl];
};

// pernicious depraved implementation file x.cpp
#include "x.h"
X::X()
{
  assert( sizeofximpl >= sizeof(XImpl) );
  new (&pimpl_[0]) XImpl;
}
X::~X()
{
  (reinterpret_cast<XImpl*>(&pimpl_[0]))->~XImpl();
}

이런 짓을 하지 말라. 정렬 제한이 맞지 않고, 매우 불안정하며, 유지보수하기 힘들고, 비효율적이며, 애초부터 틀렸다.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Google photo

Google의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 /  변경 )

%s에 연결하는 중