はじめに
あなたはスマートポインタというものを知り、shared_ptr
でクラスを管理することを試みます。また、今までthis
で行っていたように、自身のshared_ptr
を取得したいです。単純にshared_ptr(this)
とするとエラーになってしまいます。インターネットで調べると、enable_shared_from_this
というものを継承させればいいということを知ります。しかしこれは罠です。調べると色々出てくるので割愛しますがenable_shared_from_this
には様々な問題があります。有識者に聞くと「enable_shared_from_this
なんて使わないよ?」と言われ、じゃあ僕の設計が間違っているのか…?となります。(ここまで実体験)
この問題にはわかりやすく画期的な解決策があり、しかもそれはかなり著名なC++設計法として知られています。
それは ズバリshared_ptr
をラップすることです。
解決策
いまこういう状況です。
class Player
{
public:
void update(double dt)
{
/*
いろんな処理
*/
//std::shared_ptr<Player> player = ... // update()内で自身の shared_ptr がほしい。
/*
いろんな処理
*/
}
};
Playerのラッパークラスを作ります。
元のPlayerクラスを ➝ PlayerDetail
Playerのラッパークラスを ➝ Player
という風に命名します。
//元のPlayerクラス
class PlayerDetail
{
public:
void update(double dt)
{
/*
いろんな処理
*/
//std::shared_ptr<PlayerDetail> player = ... // update()内で自身の shared_ptr がほしい。
/*
いろんな処理
*/
}
};
//元のPlayerクラスのラッパー
class Player
{
public:
Player()
:player(std::make_shared<PlayerDetail>()) // コンストラクタでshared_ptrを作成。
{
}
void update(double dt)
{
player->update(dt);//ラッパークラスでもupdate()を定義し、中でPlayerDetailのupdate()を呼びます
}
private:
std::shared_ptr<PlayerDetail> player;//shared_ptrをラップしている。
};
まだラッパークラスを作成しただけでなんの解決にもなっていません。
そこで、PlayerDetail
のupdate()にshared_ptr<PlayerDetail>
の引数を追加し、ラッパークラスでupdate()を呼び出す際に渡してあげます。
//元のPlayerクラス
class PlayerDetail
{
public:
void update(const std::shared_ptr<PlayerDetail>& player, double dt)
{
/*
いろんな処理
*/
// 自身の shared_ptr である player が使える!!!
/*
いろんな処理
*/
}
};
//元のPlayerクラスのラッパー
class Player
{
public:
Player()
:player(std::make_shared<PlayerDetail>())
{
}
void update(double dt)
{
player->update(player, dt);//playerを渡してあげる
}
private:
std::shared_ptr<PlayerDetail> player;
};
なんということでしょう。問題が解決しました!
update()以外のメンバ関数でも使いたい場合は、その都度引数にstd::shared_ptr<PlayerDetail>
を持たせ、ラッパークラスから渡してあげます。
メンバ関数を増やしたら、その都度ラッパークラスのメンバ関数も増やさないといけないのは少し手間ですが、そこは目をつぶりましょう。
ラッパークラスを作ると
std::vector<std::shared_ptr<Player>> players;
こうやって書いてたのが
std::vector<Player> players;
こうでよくなったりするなど、多くの場面でいちいちshared_ptr
と書かなくて良くなり、型の見た目もスッキリするという効果もあります。
実は
このような実装は「pImplイディオム」として知られています。pImplイディオムについては他の記事を調べてください。何故かどの記事でも自身のshared_ptrを渡せることについて全く言及していませんが、それを抜きにしても様々なメリットがあるようです。
private:
- std::shared_ptr<PlayerDetail> player;
+ std::shared_ptr<PlayerDetail> pImpl;
今回はplayer
として持たせていましたが、pImpl
と命名するとpImplイディオムであることが分かりやすくなり、他者からの可読性向上が見込めます。