質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.34%

  • C++

    3746questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

C++でオブジェクトの参照(ポインタ)を別のクラスに持たせたい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 249

ai2playgame

score 4

 前提・実現したいこと

OpenSiv3Dで簡単なゲームを開発しています。

そこでUIにPlayerオブジェクトのHPを常時表示させたいと考えています。
UIクラスにstd::unique_ptr<Player>変数を持たせ、コンストラクタで生成したオブジェクトを登録しようとしたのですが、そこでコピーが起きているのか、実際はHPが減っているのに、デフォルトの値から変更されません。

何が間違っている & 他に良い手段はないでしょうか?よければ教えてください。

 該当のソースコード(一部略)

#include <Siv3D.hpp>

class Player
{
private:
    uint32 m_hp;

public:
    Player(uint32 hp = 4)
        :m_hp(hp) {}

    void hp_minus()
    {
        this->m_hp--;
    }

    auto hp()
    {
        return this->m_hp;
    }

    void debug()
    {
        Print(U"player.HP");
        Print(this->m_hp);
    }
};

class UI
{
private:
    using PlayerPtr = std::shared_ptr<Player>;
    PlayerPtr m_playerPtr;

public:
    UI() = default;
    UI(Player& player)
    {
        m_playerPtr = std::make_shared<Player>(player);
    }

    void debug()
    {
        Print(U"UI.playerPtr->HP");
        Print(this->m_playerPtr->hp());
    }
};

void Main()
{
    Player player(4);
    UI ui(player);
    player.debug();
    ui.debug();

    player.hp_minus();
    player.debug();
    ui.debug();

    while (System::Update())
    {

    }
}

 出力

player.HP
4
UI.playerPtr->HP
4
player.HP
3
UI.playerPtr->HP
4

 補足情報

Visual Studio 2017
OpenSiv3D

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+2

こんにちは。

std::shared_ptrは原則としてnewで直接確保したオブジェクトだけを管理できます。つまり、静的領域(グローバル変数やstatic変数)やスタック上に獲得したオブジェクトなどを管理することができないのです。
従って、main()で獲得しているPlayer player(4);をstd::shared_ptrで管理することはできません。

できないことをやろうとするとコンパイル・エラーになるように設計されている場合が多いのですが、残念ながらstd::make_sharedはこのケースではエラーを出さずに渡したplayerオブジェクトをコピーして、コピーした方の領域を管理します。

ここを見ると、

型Tに対する shared_ptr<T>オブジェクトを生成し返却する。
このとき、args... で受け取った引数リストを型 Tのコンストラクタへ渡してshared_ptr<T>型のオブジェクトを生成する。

と書かれています。この場合、Playerオブジェクトへの参照を受け取るPlayerのコンストラクタが呼び出されます。
「そんなコンストラクタは定義していないじゃないか!」と言いたいでしょう。確かに明示的には定義されていません。しかし、C++コンパイラは結構多くのケースでコピー・コンストラクタを自動的に定義します。(このおかげで安易にオブジェクトをコピーできるので実にありがたい機能です。)
しかし、今回はそれが意図しない形で働いてしまいました。std::make_sharedは受け取ったplayerオブジェクトをPlayerクラスのコピー・コンストラクタに渡すのでこのタイミングでコピーされていまいます。

UIクラスのコンストラクタでPlayerへの参照を受け取るのではなく、uint32 hpを受け取ると意図通りに動作する筈です。main()関数側はUIオブジェクト内のm_playerPtrをコピーして使うと良いと思います。

(std::shared_ptrをコピーしても、それが管理する領域はコピーされませんのでご安心を。)

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/26 02:53 編集

    回答有り難うございます。

    C++に触れて以来shared_ptrについて、よく分かっていなかったことが理解出来、非常に感謝しています。

    ……もう少し、質問よろしいでしょうか?

    > UIクラスのコンストラクタでPlayerへの参照を受け取るのではなく、uint32 hpを受け取ると意図通りに動作する筈です。main()関数側はUIオブジェクト内のm_playerPtrをコピーして使うと良いと思います。

    こちらについて実際どう実装してよいのか分かりません。uit32 hpをUIクラスのコンストラクタ引数としてとるのは分かりますが、それをどうコンストラクタ内で使うのか、main関数内でm_playerPtrのコピーとは何なのか。

    加えて今後UIクラスにはPlayer.score等、追加でplayerクラスの情報を表示出来るようにしたいのです。そうなった時にはどうすればよいのか。

    重ね重ねになりますが回答頂けると助かります。

    (追記)自分なりに考えてみました。


    ```cpp

    #include <Siv3D.hpp>

    class Player
    {
    private:
    uint32 m_hp;

    public:
    Player(uint32 hp = 4)
    :m_hp(hp) {}

    void hp_minus()
    {
    this->m_hp--;
    }

    auto hp()
    {
    return this->m_hp;
    }

    void debug()
    {
    Print(U"player.HP");
    Print(this->m_hp);
    }
    };

    class UI
    {
    private:
    using PlayerPtr = std::shared_ptr<Player>;
    PlayerPtr m_playerPtr;

    public:
    UI() = default;
    UI(uint32 hp)
    {
    m_playerPtr = std::make_shared<Player>(hp);
    }

    void debug()
    {
    Print(U"UI.playerPtr->HP");
    Print(this->m_playerPtr->hp());
    }

    auto playerPtr()
    {
    return m_playerPtr;
    }
    };

    void Main()
    {
    UI ui(4);
    ui.playerPtr() -> debug();
    ui.debug();

    ui.playerPtr()->hp_minus();
    ui.playerPtr()->debug();
    ui.debug();

    while (System::Update())
    {

    }
    }

    ```

    キャンセル

  • 2018/07/26 04:11

    UIのコンストラクタの話はそれでOKです。

    main()でコピーを保持する件は、例えば下記です。
    int main()
    {
      UI ui(4);
      std::shared_ptr<Player> player = ui.playerPtr();
      player->debug();
      ui.debug();

      player->hp_minus();
      player->debug();
      ui.debug();
    以下略

    > 加えて今後UIクラスにはPlayer.score等、追加でplayerクラスの情報を表示出来るようにしたいのです。そうなった時にはどうすればよいのか。

    普通に表示すれば良いですけど、何が悩ましいのでしょうか?

    キャンセル

  • 2018/07/29 01:09

    確認遅れました。回答有り難うございます。

    キャンセル

同じタグがついた質問を見る

  • C++

    3746questions

    C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。