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

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

ただいまの
回答率

90.83%

  • C++

    3142questions

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

メンバ変数をポインタで持つか実体で持つか

解決済

回答 6

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 408

nozomin_jp

score 5

メンバ変数を実体で持つか、ポインタで持つか、で悩んでおります。

ゲームに登場するオブジェクトの基底クラスを作成しているのですが、
例えば、下記のような場合、Vector3、BoundingCircleは実体で持つべきでしょうか。ポインタで持つべきでしょうか。

実体で持つ利点はオブジェクト生成時に一緒に作成される為nullチェックが必要ないこと、クラス破棄時に一緒に捨てられることだと思っております。
デメリットとしてはヘッダのインクルードが増えてしまうこと、循環参照が起きる可能性があることだと思っております。

ポインタで持つ場合の利点は、生成を遅らせることができる、ヘッダインクルードを減らせる、オブジェクトの差し替えがポインタの入れ替えで済む、だと思っております。
デメリットとしてはnullチェックが必要であることだと思っております。

どちらでも実装することは可能な為、どのように選択すればよいのか迷っております。
ゲームのプラットフォームはPCです。
可能であれば、なぜそう選択するのか理由も合わせて答えて頂けると助かります。

よろしくお願い致します。

class BaseEntity {
public:
//------------------------------------------------
// デフォルトコンストラクタ
//------------------------------------------------
BaseEntity( int type, const Vector3& pos )
: mID( id++ )
, mType( type )
, mPosition( pos )
, mBoundingCircle( bc ) {}

//------------------------------------------------
// 更新
//------------------------------------------------
virtual void update(){};

//------------------------------------------------
// 描画
//------------------------------------------------
virtual void draw(){};

// getter
//------------------------------------------------
// IDを返す
//------------------------------------------------
constexpr int getID() const noexcept {
return mID;
}

//------------------------------------------------
// Typeを返す
//------------------------------------------------
constexpr int getType() const noexcept {
return mType;
}

//------------------------------------------------
// 位置を返す
//------------------------------------------------
constexpr Vector3 getPosition() const noexcept {
return mPosition;
}

//------------------------------------------------
// コライダを返す
//------------------------------------------------
BoundingCircle getCollider() const noexcept {
return mBoundingCircle;
}

// setter
//------------------------------------------------
// Typeを設定する
//------------------------------------------------
constexpr void setType( int type ) noexcept {
mType = type;
}

//------------------------------------------------
// Positionを設定する
//------------------------------------------------
constexpr void setPositon( const Vector3& pos ) noexcept {
mPosition = pos;
}

//------------------------------------------------
// コライダを設定する
//------------------------------------------------
constexpr void setCollider( const BoundingCircle& bc ) noexcept {
mBoundingCircle = bc;
}

private:
static int id;                               // オブジェクト生成時に増加させる為、静的
int     mID;                                // 一意に識別する為のID
int     mType;                        // オブジェクトのタイプ
Vector3 mPosition;                    // 位置
BoundingCircle mBoundingCircle;  // 当たり判定

}

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 6

+3

例えば、下記のような場合、Vector3、BoundingCircleは実体で持つべきでしょうか。ポインタで持つべきでしょうか。

この例であれば「実体で持つ」、つまり質問中コードの通り値型データメンバで保持することをおすすめします。

設計の観点: 位置(Vector3)や当たり判定用の外接円?(BoundingCircle)といった情報は、このアプリケーション共通部品(BaseEntity)に強く紐づいた属性ですから、それぞれのクラス実体のラフサイクル管理を一体で捉えるべきと考えます。オブジェクト指向設計的には “has-a” 関係として説明されるものです。

実装の観点: 名前と説明文からの推測ですが、Vector3BoundingCircleは2~3個の数値型から構成される小さなクラス(構造体)と予想されます。このようなデータ構造を保持する場合、動的確保+ポインタ構造ではメモリ空間を浪費し、ポインタを介した変数アクセス・コストが相対的に大きくなってしまます。実体としてBaseEntityクラスに内包してしまえば、このようなオーバーヘッドは全て解消します。

いずれにせよ位置情報や当たり判定情報をprivateメンバとして保持していますから、BaseEntityクラスの内部実装を後から変更する(実体で持つ→ポインタで持つ)ことは、同クラスの利用者にとっては影響がありません。何らかの理由で後からポインタで保持したいとなっても、そのときに内部実装を切り替えれば十分かと思います。


ポインタで持つ場合の利点は、生成を遅らせることができる、
デメリットとしてはnullチェックが必要であることだと思っております。 

またnullチェックへの言及がありますが、設計上は「位置や当たり判定を持たないBaseEntityの存在を許すか」で判断すべきかと思います。答えがNOならばnullチェックはassert文で十分、つまりプログラムのバグ以外では気にする必要がありません。YESならば、std::unique_ptrなどの(スマート)ポインタ型や、std::optional<T>といった無効/有効値を表すデータ型の利用を検討してみてください。


デメリットとしてはヘッダのインクルードが増えてしまうこと、循環参照が起きる可能性があることだと思っております。 

(あくまで推測ですが)Vector3BoundingCircleなどのクラスは、このアプリケーション(ゲーム)を構成するネジ・クギのような非常にプリミティブな共有部品のはずです。このような部品を宣言するヘッダで循環参照が起きるということは考えにくいですし、もし起きてしまうようならヘッダファイル構成を再考すべきでしょう。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

これはゲーム用の管理クラスか何かですかね?

当たり判定やら座標計算やらというのは、膨大な回数計算するのが常ですので、ポインタにすると、普段は十分に無視可能な関節参照コストと、キャッシュの局所性が無視できなくなります。

結局コンパイル時間を取るか、実行速度を取るかだと思います。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

checkベストアンサー

0

ポインターにしなければならない特別な事情・明確な理由がない(思いつかない)のであれば、実体で持つべきです。

まず、実体で持つことのデメリットにヘッダーのインクルードが増えることを挙げていますが、それに問題があるのでしょうか。ドライブの転送速度が『10MB/s!超速っ!!』とか言っていた時代ならともかく、今時のPCなら転送速度は桁違いですし(SSDともなればさらに)、メモリもおそらく全ヘッダーをキャッシュできてしまえるでしょう。そもそも、インクルードすべきヘッダーは標準ライブラリーやフレームワーク、SDKなど多数あるわけで、自分が作ったいくつかのヘッダーが増えたところで体感的にはほとんど影響ないと思います(自分の作ったヘッダーが膨大な数に上るのであればまた別ですが……)。

また、循環参照が起きる可能性については、それは設計の問題としてクリアにすべきであって、「なんかよくわかんないけど循環参照が起きるかもしれない」のであれば、まずは設計(ヘッダーのクラス定義)を確認し、その疑問を解消してください。

あと、ポインターの利点として、オブジェクトの差し替えがポインターの入れ替えで済むことだとしていますが、ポインターで持つ場合は別のコストがかかります。まず単純にnew/deleteはパフォーマンス的に高コストな命令です。実体で持てばBaseEntityだけをnew/deleteすれば良いところを、ポインターで持つとそれらもnew/deleteしないといけないのでその分コストが増加します。また、オブジェクトのやりとりをポインター操作で行おうとすると所有権問題が発生しますから、スマートポインター(特に共有ポインター等)の導入の検討など、設計コストの増加も考えられます。おそらくこちらの方がインパクトとしては大きいでしょう。設計が複雑化すればバグのリスクも増加します。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

こんにちは。

私もこのケースなら、実体で持つ方を選択します。
といいますか、どちらでもOKなら実体で持ちます。ポインタ(というかnew/delete)は性能を劣化させますし、メモリ・フラグメントのリスクもはらみます。対応するべき要件がない時に性能を劣化させてまでやるのは如何なものか?と感じます。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

0

ケースバイケースなので一概には決まりません。

実行速度、省メモリ優先ならば実体。
拡張性優先ならばポインタ。

が、一般的だと思います。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

-1

https://stackoverflow.com/questions/3871429/class-members-that-are-objects-pointers-or-not-c

https://stackoverflow.com/questions/27280358/define-member-variable-as-pointer-or-object

https://stackoverflow.com/questions/29013560/objects-as-member-variables-in-a-class-in-c

ちゃんと管理できるのであればポインタの方が良い気がします。

最近のポインタが頭が良いことと、リソースの消費が有利であることを鑑みて。

詳しくはリンク先にいろいろと理由があるので参考になるかと。


逆に古きポインタしか使えないのなら少しためらいますが…

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

  • ただいまの回答率 90.83%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

  • 解決済

    英文ファイルを表示

    ある英文が与えられたファイルがあり,それを所有格や空白記号類を削除し単語一つ一つを改行して表示していくプログラムを考えています. 実行結果の例としては This is a pen.

  • 受付中

    二次元配列のポインタを返すメソッド

    現在C++でMFCプログラミングをしています。 2次元配列のポインタを戻り値とする関数を作成したのですが プロトタイプ宣言がとても意味不明なことになっています。 おすすめの宣言の仕

  • 受付中

    C#でpictureBoxの上に編集用の枠を描画したい

    pictureBoxの上に画像にあるような編集用の枠(移動・スケールあり)を作りたいと思っていますが、このような描画を作ったことがないのでどのように作るのかお教えいただければと思い

  • 解決済

    基底クラスのポインタから派生クラスの型を特定する

    前提・実現したいこと C++についての質問です。 基底クラスのポインタから派生クラスの型を特定することは可能でしょうか? 実際のソース Collisionという基底クラ

  • 解決済

    [C++] 配列の使い分け?について

    こんにちは。 あらかじめ配列の要素数が分かっているわけではなく、コンストラクタの引数によって要素数が決定し、それ以降要素数は変化しないような場合は、std:vectorを固定

  • 解決済

    STLに対してObserverパターンを実現するには?

    例えばstd::vectorでpush_back等の関数が呼ばれた時に同時に別の関数を呼び出したいのですが、どうやって実現すればいいのか分かりません。 STLのクラスを継承する

  • 受付中

    C++ オブジェクト指向 staticの使用について

    C++前提で話します。 static修飾子がありますが、classを使っていてstaticが必要だと思ったことがありません。 staticでできることは理解していますがやはり使

  • 解決済

    C++ ヘッダの実装部について

    こんにちは ヘッダの実装部についての質問です。 以下のようなコードがうまく実行されません。 a.h-------------------------------------

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

  • C++

    3142questions

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