Pimple idiom

Tags:

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 를 다뤄줘야 한다는 점입니다.