pImplイディオム実例

beatle
記事: 1281
登録日時: 14年前
住所: 埼玉
連絡を取る:

pImplイディオム実例

投稿記事 by beatle » 13年前

ヘッダファイルの依存関係を減らすpImplイディオム。
その学習のために、実例を作ってみました。

FieldPlayer.hpp

CODE:

#ifndef FIELDPLAYER_HPP_
#define FIELDPLAYER_HPP_

class FieldPlayer
{
public:
    FieldPlayer();

    void Draw();
    int Update();

    int model_handle() const;

private:
    int model_handle_;
};

#endif // FIELDPLAYER_HPP_
FieldPlayer.cpp

CODE:

#include "FieldPlayer.hpp"
#include 

FieldPlayer::FieldPlayer()
    : model_handle_(-1)
{
}

void FieldPlayer::Draw()
{
    if (model_handle_ != -1)
    {
        MV1DrawModel(model_handle_);
    }
}

int FieldPlayer::Update()
{
    return 0;
}

int FieldPlayer::model_handle() const
{
    return model_handle_;
}
これは、プレイヤーキャラクターを画面に描画するクラスです。見ての通りまだまだ未完成ですね。
これから、このクラスにプレイヤーの位置を管理する機能を追加します。

FieldPlayer.hpp

CODE:

#ifndef FIELDPLAYER_HPP_
#define FIELDPLAYER_HPP_

#include 

class FieldPlayer
{
public:
    FieldPlayer();

    void Draw();
    int Update();

    int model_handle() const;

private:
    int model_handle_;
    VECTOR pos_; // これを追加
};

#endif // FIELDPLAYER_HPP_
FieldPlayer.cpp

CODE:

#include "FieldPlayer.hpp"
#include 

FieldPlayer::FieldPlayer()
    : model_handle_(-1), pos_() // これを追加
{
}

void FieldPlayer::Draw()
{
    if (model_handle_ != -1)
    {
        MV1DrawModel(model_handle_);
    }
}

int FieldPlayer::Update()
{
    if (model_handle_ != -1) // これを追加
    {
        pos_.x += 0.1f;
        MV1SetPosition(model_handle_, pos_);
    }
    return 0;
}

int FieldPlayer::model_handle() const
{
    return model_handle_;
}
注目すべきは、FieldPlayerクラスのpublicなインターフェス(DrawやUpdate)は変更しておらず、内部実装だけを変更しているにも関わらず、FieldPlayer.hppが変更されてしまっていることです。FieldPlayer.hppに、メンバ変数という「実装部分」が露出してしまっているというわけです。
このこと自体は、C++をやっている人には至極当たり前のことでしょう。

FieldPlayer.hppに実装が露出しているというのも問題ですが、もっと実際的な問題としてはFieldPlayer.hppを#includeしているすべてのcppファイルをコンパイルし直さないといけないということです。
内部実装を変更しただけなのに、膨大な再コンパイル時間がかかるというのは、作業効率を低下させます。

そこで、pImplイディオムを用いて書き換えてみましょう。以下、pos_を追加する前のソースのpImpl版です。

FieldPlayer.hpp

CODE:

#ifndef FIELDPLAYER_HPP_
#define FIELDPLAYER_HPP_

#include 

class FieldPlayer
{
public:
    FieldPlayer();
    ~FieldPlayer(); // pImplイディオムでstd::unique_ptrを使う場合、明示的にデストラクタを宣言するのが重要。

    void Draw(); // 公開インターフェース
    int Update(); // 公開インターフェース

    int model_handle() const; // 公開インターフェース

private:
    class Impl;
    std::unique_ptr impl_; // 実装へのポインタを持つ
};

#endif // FIELDPLAYER_HPP_
FieldPlayer.cpp

CODE:

#include "FieldPlayer.hpp"
#include 

class FieldPlayer::Impl
{
public:
    Impl();

    void Draw();
    int Update();

    int model_handle() const;

private:
    int model_handle_;
};

FieldPlayer::Impl::Impl()
    : model_handle_(-1)
{
}

void FieldPlayer::Impl::Draw()
{
    if (model_handle_ != -1)
    {
        MV1DrawModel(model_handle_);
    }
}

int FieldPlayer::Impl::Update()
{
    return 0;
}

int FieldPlayer::Impl::model_handle() const
{
    return model_handle_;
}

FieldPlayer::FieldPlayer()
    : impl_(new Impl())
{
}

FieldPlayer::~FieldPlayer()
{
}

void FieldPlayer::Draw()
{
    impl_->Draw();
}

int FieldPlayer::Update()
{
    return impl_->Update();
}

int FieldPlayer::model_handle() const
{
    return impl_->model_handle();
}
先ほどと同じように、モデルの位置を実装してみます。

FieldPlayer.cpp

CODE:

#include "FieldPlayer.hpp"
#include 

class FieldPlayer::Impl
{
public:
    Impl();

    void Draw();
    int Update();

    int model_handle() const;

private:
    int model_handle_;
    VECTOR pos_; // これを追加
};

FieldPlayer::Impl::Impl()
    : model_handle_(-1), pos_() // これを追加
{
}

void FieldPlayer::Impl::Draw()
{
    if (model_handle_ != -1)
    {
        MV1DrawModel(model_handle_);
    }
}

int FieldPlayer::Impl::Update()
{
    if (model_handle_ != -1) // これを追加
    {
        pos_.x += 0.1f;
        MV1SetPosition(model_handle_, pos_);
    }
    return 0;
}

int FieldPlayer::Impl::model_handle() const
{
    return model_handle_;
}

FieldPlayer::FieldPlayer()
    : impl_(new Impl())
{
}

FieldPlayer::~FieldPlayer()
{
}

void FieldPlayer::Draw()
{
    impl_->Draw();
}

int FieldPlayer::Update()
{
    return impl_->Update();
}

int FieldPlayer::model_handle() const
{
    return impl_->model_handle();
}
FieldPlayer.hppを載せていないのは、何も変更がないからです。そう、これがミソ!FieldPlayerクラスは単なるインターフェスで、実装はFieldPlayer::Implクラスですから、FieldPlayer.hppを変更する必要がないのです。

このように、pImplイディオムを用いると、実装を書き換えた場合にヘッダファイルを修正する頻度が劇的に減ります。
(もちろん、public関数を追加したりプロトタイプ宣言を変えたりするには、ヘッダファイルの変更と、それに伴う再コンパイルが必要ですが。)
最後に編集したユーザー beatle on 2012年3月29日(木) 11:18 [ 編集 2 回目 ]

アバター
GRAM
記事: 164
登録日時: 15年前

Re: pImplイディオム実例

投稿記事 by GRAM » 13年前

デストラクタが必須のときは忘れないのですが、
空のデストラクタでいい時に、たまに書くの忘れて、コンパイルで「あ~~~またやった」ってなります