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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C++

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

Q&A

解決済

6回答

14256閲覧

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

nozomin_jp

総合スコア13

C++

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

0グッド

2クリップ

投稿2018/06/03 22:18

編集2018/06/03 22:28

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

ゲームに登場するオブジェクトの基底クラスを作成しているのですが、
例えば、下記のような場合、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; // 当たり判定

}

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答6

0

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

投稿2018/06/04 01:39

編集2018/06/04 01:51
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

投稿2018/06/04 00:04

編集2018/06/04 00:05
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

投稿2018/06/04 10:26

HogeAnimalLover

総合スコア4830

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

ベストアンサー

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

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

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

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

投稿2018/06/04 04:55

catsforepaw

総合スコア5938

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

こんにちは。

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

投稿2018/06/04 02:09

Chironian

総合スコア23272

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

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

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

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

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


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

投稿2018/06/03 23:11

mkgrei

総合スコア8560

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問