http://www.cs.usyd.edu.au/~sholden/TEACHING/PP/2001sem2/ADVANCED/pimples/
http://www1.bell-labs.com/user/cope/Patterns/C++Idioms/EuroPLoP98.html
aka compilation firewall, Cheshire Cat, and handle/body hierarchy.
첫번째 링크의 내용을 요약하자면 아래와 같습니다.
예를들어,
/* header */
class simple_class {
public:
simple_class(int a, int b);
int get_first();
int get_second();
int get_difference();
private:
int a;
int b;
};
/* source file */
#include "simple_class.h"
simple_class::simple_class(int a, int b) : a(a), b(b) {}
int simple_class::get_first() { return a; }
int simple_class::get_second() { return b; }
int simple_class::get_difference() { return b-a; }
이 있다고 할 때 이를 사용하는 main.cpp 가 아래와 같다고 해보죠.
#include "simple_class.h"
#include <iostream>
int main() {
simple_class one(5,10);
simple_class two(10,20);
simple_class three(20,40);
std::cout << one.get_first() << ' ' << one.get_second()
<< ' ' << one.get_difference() << '\n';
std::cout << two.get_first() << ' ' << two.get_second()
<< ' ' << two.get_difference() << '\n';
std::cout << three.get_first() << ' ' << three.get_second()
<< ' ' << three.get_difference() << '\n';
}
컴파일 및 실행은 다음과 같이 합니다.
g++ -c main.cc
g++ -c simple_class.cc
g++ main.o simple_class.o -o first
여기까지는 문제가 없죠. 그리고 이것이 C++이 헤더와 소스파일을 분리해서 구현을 숨기는 방법이기도 하고요. 하지만, 정말로 C++ 이 소스를 숨겨주는 것은 아닙니다. 예를들어, 어떤 라이브러리를 만들어 사용자에게 제공하고자한다면, 분명히 앞서 나온 header file은 사용자에게 주어야만 하는데 이 경우, 내부 구현이 드러나는 문제가 있습니다. 또다른 문제는 이렇게 파일은 분리가 되어있지만, 실제로 main 함수가 simple_class.h 에 대해 의존하게 된다는 점입니다. 그리고 이런 의존관계로 인해 simple_class.h 가 수정되면 모든 프로그램(이 경우엔 main)도 재컴파일되야하는 문제가 발생합니다.
예를들어,
/* 바뀐 header */
class simple_class {
public:
simple_class(int a, int b);
int get_first();
int get_second();
int get_difference();
private:
int a;
int b;
int dif;
};
/* 바뀐 source code */
#include "simple_class.h"
/* or #include "simple_class2.h" depending on what you named the files...*/
simple_class::simple_class(int a, int b) : a(a), b(b), dif(b-a) {}
int simple_class::get_first() { return a; }
int simple_class::get_second() { return b; }
int simple_class::get_difference() { return dif; }
만약 다음과 같이 선택적으로 재컴파일 한다면…
g++ -c simple_class.cc
g++ main.o simple_class.o -o second
아무런 에러도 없이 main은 잘못된 결과를 출력해버립니다. 이 문제를 해결하려면,
g++ -c main.cc
g++ main.o simple_class.o -o third
와 같이 main도 재컴파일 해야하는 문제가 생기죠.
이를 해결하는 것이 Pimple Idiom 입니다. 여기서 Pimpl은 Pointer to IMPLementation의 약자이고, 구현은 다음과 같이 합니다.
/* header file */
class simple_class_implementation;
class simple_class {
public:
simple_class(int a, int b);
~simple_class();
int get_first();
int get_second();
int get_difference();
private:
simple_class_implementation *impl;
};
/* source code file */
#include "simple_class.h"
class simple_class_implementation {
private:
friend class simple_class;
simple_class_implementation(int a, int b) : a(a), b(b) {}
int get_first() { return a; }
int get_second() { return b; }
int get_difference() { return b-a; }
int a;
int b;
};
simple_class::simple_class(int a, int b) :
impl(new simple_class_implementation(a,b)) {}
simple_class::~simple_class() {
delete impl;
}
int simple_class::get_first() {
return impl->get_first();
}
int simple_class::get_second() {
return impl->get_second();
}
int simple_class::get_difference() {
return impl->get_difference();
}
눈여겨 볼점은, simple_class가 생성되면서 impl 에 대해 new를 해주고, simple_class가 소멸될 때 impl에 delete를 해주어야 한다는 점입니다. 또, simple_class.h 에서 private에 대한 정보가 모두 사라지게 되었구요. 이제는 simple_class에 대한 구현이 수정되어도, 전체를 재컴파일 할 필요가 없게 됩니다. 그리고 구현은 ‘소스 코드 (즉 cpp파일)’ 안으로 숨습니다.
단점은 another indirection으로 인한 오버헤드, 길어진 코드로 인한 에러 가능성, new와 delete 를 다뤄줘야 한다는 점입니다.