C++ - 자주 사용하는 C++ 이디엄(idiom) pimpl

Pimpl(Pointer to IMPLementation)은 구현을 숨기거나, 헤더를 바꾸지 않는 것으로 컴파일을 속도를 고속화 할 때 사용한다.

C++20의 module에서는 필요 없어지겠지만 아직은 필요하다

hoge.h

class Hoge {
    int foo_;
    void baz();
public:
    int bar();
    Hoge() = default;
    ~Hoge() = default;
};

hoge.cpp

#include "hoge.h"

void Hoge::baz()
{
    // 처리
}
int Hoge::bar()
{
    // 처리
    baz();
}

이 클래스에서 새로운 piyo라는 변수를 추가하고 싶은 경우 private 변수로 하더라도 헤더를 다시 써야 한다.
헤더를 다시 쓰지 않으면 안 되는 것은 이것을 읽고 있는 소스 파일을 다시 한번 컴파일 해야 한다는 것이다.

Pimpl 이디엄을 사용하면 이런 문제를 해결할 수 있다.

hoge.h

#include <memory>

class Hoge {
    class HogeImpl; //전방 선언만 한다
    std::unique_ptr<HogeImpl> impl_; // 이 클래스에서만 사용하므로 unique_ptr을 사용

public:
    Hoge(); 
    ~Hoge(); // pimpl의 경우 여기에 소멸자를 쓸 수 없다.
    int baz();
};  

hoge.cpp

#include <memory>
#include "hoge.h“

class Hoge::HogeImpl
{
    int foo_;
    void baz() { // 처리 }
Public:
    int bar() { baz(); // 처리 }
};

Hoge::Hoge() : impl_(std::make_unique<HogeImpl>()) { }

Hoge::~Hoge() = default;

int Hoge::bar()
{
    return impl_>bar();
}

소멸자를 헤더에 쓰지 않는 것은 Hogen의 소멸자에서 unique_ptr이 HogeImpl의 소멸자를 호출하기 바라지만 불완전한 클래스인 HogeImpl의 소멸자를 부를 수 없기 때문이다.

pimpl을 사용하면 baz 멤버 함수의 행동을 바꾸기 위해서 적당한 private 변수를 추가하고 싶다든가 할 때에 헤더 파일을 갱신할 필요가 없다.

다음과 같이 하면 된다.

// 생략
Class Hoge::HogeImpl
{
    int foo_;
    int qux_;
    void baz() { qux_ = 1; //처리}

Public:
    int bar() { baz(); // 처리}
};
// 생략

pimpl의 약점

  • 상속 하기 어렵다
  • 원래의 코드보다 (약간)속도가 떨어질 수 있다.

이 글은 2020-05-25에 작성되었습니다.