C++のPimplイディオム

最近読んだ『C++のためのAPIデザイン』という本に興味深い技法が紹介されていてちょっと感動したので備忘録。

きっかけ

C++で、あるクラスのヘッダを以下のように書いたとしましょう。

MyClass.h

#ifndef MyClass_h
#define MyClass_h

class MyClass
{
public:
    MyClass();
    virtual ~MyClass();
    
private:
    // API利用者に使わせたくない関数
    void privateFunc();
};

#endif

このとき、MyClassの利用者には「privateFunc()」というメンバ関数の存在が丸見えになってしまっています。

利用者は人様が作ったクラスのアクセス指定子を無効化する手段を持っているため、たとえば以下のように書くと、なんとMyClassの外側から合法的にprivateメンバにアクセスできてしまいます。こわいですね。

#define private public
#include "MyClass.h"
#undef private

int main(int argc, const char * argv[])
{
    MyClass myClass;   
    myClass.privateFunc();
    
    return 0;
}

では、どうやってこの問題を解決すればよいのでしょうか。

Pimplイディオムの導入

基本的な方針は、MyClass.hの中身のうちprivateなメンバを丸ごとImplに委譲してしまう事です。

MyClass.h

#ifndef MyClass_h
#define MyClass_h

class MyClass
{
public:
    MyClass();
    virtual ~MyClass();
    
private:
    // コピーコンストラクタと代入演算子を封印する
    MyClass(const MyClass &);
    const MyClass &operator =(const MyClass &);
    
    // 元のprivateな処理はImplに委譲する
    class Impl;
    Impl *mImpl;
};

#endif

このヘッダにおいて、Implは前方宣言されているに過ぎないため、外からはmImplに関する一切の詳細が隠蔽されています。ここがポイント。

続いて、実装のコードは以下のようになります。

MyClass.cpp

#include <iostream>
#include "MyClass.h"

class MyClass::Impl {
public:
    // API利用者に使わせたくない関数
    void privateFunc() const {
        std::cout << "こんにちは世界" << std::endl;
    }
};

MyClass::MyClass() {
    mImpl = new Impl();
}

MyClass::~MyClass() {
    delete mImpl;
}

このようにする事で、API利用者からはprivateFunc()の存在が見えなくなります。

先ほども書きましたが、利用者にとってmImplというメンバ変数は見えていても、その先は隠蔽されていますので、以下のようなコードはコンパイルエラーになります。

#define private public
#include "MyClass.h"
#undef private

int main(int argc, const char * argv[])
{

    MyClass myClass;
    myClass.mImpl->privateFunc();   // これはできない
    
    return 0;
}

ちなみに、MyClassからはmImplメンバを経由してprivateFunc()を実行する事が可能です。

試しにMyClassのコンストラクタを以下のように書き換えると、期待した動作になる事が確認できるはずです。

MyClass::MyClass() {
    mImpl = new Impl();
    
    mImpl->privateFunc();
}

めでたしめでたし。

Copyright (c) 2012 @tercel_s, @iTercel, @pi_cro_s.