0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

shared_ptrで管理されたクラス内でthis(自身のshared_ptr)を扱いたいときの落とし穴

Posted at

はじめに

あなたはスマートポインタというものを知り、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イディオムであることが分かりやすくなり、他者からの可読性向上が見込めます。

0
0
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?