読者です 読者をやめる 読者になる 読者になる

ぷろみん

プログラミング的な内容を扱ってます

Pimplイディオムはアンチパターン

モチベーション

ある時、Pimplイディオムはアンチパターンという言葉を聞きました。
良いパターンだと思ってたので何でだろうと考えてみました。

Pimplイディオムの利点

class Foo {
public:
    // 公開部分
private:
    class Impl;
    std::unique_ptr<Impl> pimpl_;
};
  • 内部変数を公開しなくて良い
  • 余計なヘッダファイルを公開しなくて良い

Implクラスを使ってみよう

内部処理では簡単な計算をさせてみます。
NumberとCalculatorクラスは想像にお任せします。

// .cpp
class Foo::Impl {
public:
    Impl() : calculator_( Calculator::Add ) {
    }
    ~Impl(){}
    // 加えて
    void Add( int a ) {
        number_.emplace_back( a );
    }
    // 計算する
    int Calculator() {
        return calculator_.Execute( number_ );
    }
private:
    Calculator calculator_;
    std::vector<Number> number_;
};

Foo::Foo() : pimpl_( new Impl ) {
}

Foo::~Foo(){}

// Pimplイディオムのデメリットである冗長な処理
void Foo::Add( int a ) {
    pimpl_->Add( a );
}

int Foo::Calculator() {
    return pimpl_->Calculator();
}

FooクラスをincludeしてもNumberやCalculatorクラスは読み込まれません。良かった?

Implクラスを使わない方もやってみよう

Pimplイディオムを使わなくても 以下の様にすればNumberやCalculatorクラスは読み込まれません。

// .h
class Number;
class Calculator;

class Foo
{
public:
    // 公開部分
private:
    std::unique_ptr< std::vector<Number> > number_;
    std::unique_ptr< Calculator > calculator_;
};

あれ、動的メモリ使っても良いなら案外普通に読み込まなくできますね。
実装はどうでしょうか。

Foo::Foo() :
    number_( new std::vector<Number> ),
    calculator_( new Calculator( Calculator::Add ) )
{
}

Foo::~Foo(){}

void Foo::Add( int a ) {
    number_->emplace_back( a );
}

int Foo::Calculator() {
    return calculator_->Execute( number_ );
}

という訳で実はPimplイディオム使わない方が綺麗に書けます。

結論

もっと複雑だから必要なんだって人は素直にファイル分けして新しいクラスを書きましょう。Pimplイディオムが本当にアンチパターンになってしまっているのは実装クラスを内部に書く事によって再利用性を下げ、Implという何の役にも立たない名前をイディオムの名の元に許容してしまっている事にあります。

Pimplイディオムとは前方宣言とポインタメンバ以外の何物でも無いのです。