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

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

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

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

C++

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

Q&A

解決済

1回答

1642閲覧

メンバ変数を他のクラスのメンバに参照させたい。

otonoko

総合スコア2

参照

参照は、プログラミングにおいて変数や関数といったメモリ空間上での所在を指示するデータのことを指します。その中にはデータ自体は含まれず、他の場所にある情報を間接的に指示するプログラムです。

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

C++

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

0グッド

0クリップ

投稿2023/01/14 19:40

前提

C++を用いてプログラミングをしています。
他のクラスのメンバの参照方法についての質問です。
初めての質問なのでおかしなところもあるかもしれませんがよろしくお願いします。

発生している問題

class Aのメンバをclass Bで参照したいのですがあまり良い実現方法が思いつきませんでした。

該当のソースコード

C++

1class A{ 2public: 3 4private: 5 int mValue; 6} 7 8class B{ 9public: 10 11private: 12 const int& mReferenceValue; //クラスAのメンバ変数mValueを参照したい。 13} 14 15class User{ 16public: 17 void Execute(){ 18 A a(); 19 B b(); 20 } 21}

考えたこと

class Aにgetterを公開してclass Bのコンストラクタに渡せば解決するのですが一般的にgetterの公開はよくないとされているので躊躇っています。

C++

1class A{ 2public: 3 const int& GetValue(){ 4 return mValue; 5 } 6private: 7 int mValue; 8} 9 10class B{ 11public: 12 B(const int& value) 13 :mReferenceValue(value){} 14private: 15 cosnt int& mReferenceValue; //クラスAのメンバ変数mValueを参照したい。 16} 17 18class User{ 19public: 20 void Execute(){ 21 A a(); 22 B b(a.GetValue()); 23 } 24}

補足情報(FW/ツールのバージョンなど)

C++最新バージョン VisualStudio2022

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

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

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

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

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

hoshi-takanori

2023/01/14 20:06

「メンバ変数を他のクラスのメンバに参照させる」ってことは、あなたのお財布にお友達が直接手を突っ込んで中身を見れる状態にするってことです。あまり良いことだとは思えません。そもそも、それによって何を実現したいのでしょうか?
otonoko

2023/01/14 20:25

返答ありがとうございます。他のクラスにメンバを参照させることがあまり良くないことが分かりました、ありがとうございます。 現在私はゲームのプログラミングをしています。このプログラムでプレイヤーキャラを追尾する敵キャラクターを作りたいと考えました。 プレイヤーキャラを追尾するにはプレイヤーキャラの位置情報の参照を敵キャラに持たせる必要があるのではないかと考えこの質問をさせていただきました。
dameo

2023/01/14 20:27

他にも「メンバ変数を他のクラスのメンバに参照させる」=「class Aのメンバをclass Bで参照」には、生存期間的な制約もついてしまいますね。例えば以下のコードは破棄したオブジェクトに触ってしまっています。 #include <iostream> class A{ public: const int& GetValue(){return mValue;} private: int mValue; }; class B{ public: B(const int& value) :mReferenceValue(value){} ~B() {std::cout << mReferenceValue << std::endl;} // *** 追加 *** private: const int& mReferenceValue; }; int main() { A* pOrg = new A(); A& a = *pOrg; B b(a.GetValue()); delete pOrg; return 0; // ここでbが破棄されるが、~B()内から出力されるmReferenceValueの参照先は既に破棄されている }
otonoko

2023/01/14 21:19

返答ありがとうございます。具体例を出していただいたことで同じ変数を複数のクラスで持つことの危険性が分かりました、ありがとうございます。つまり下記のような実装の方がまだましということになるのでしょうか? class A{ public: const int& GetValue()const{return mValue;} private: int mValue; }; class B{ public: B(const A& a) :mA(a){} ~B{delete mA;} void DoSomething(){ const int& value = mA.GetValue(); /*なにかする*/ } //Aのメソッドを使う private: const A& mA; //class Aを持たせる }; class User{ public: void Excute(){ A* pOrg = new A(); A& a = *pOrg; B b(a); b.DoSomething(); }
dameo

2023/01/14 21:33

それはそれで別の問題がありますね。 #include <iostream> class A{ public: const int& GetValue()const{return mValue;} private: int mValue; }; class B{ public: B(const A& a):mA(a){} ~B(){delete &mA;} void DoSomething(){ std::cout << mA.GetValue() << std::endl; } //Aのメソッドを使う private: const A& mA; //class Aを持たせる }; int main(){ A* pOrg = new A(); A& a = *pOrg; B b(a); b.DoSomething(); delete pOrg; // 普通はここでdeleteする return 0; // ここでbを破棄すると二重開放 } ※そもそもdeleteはnewと対応しているので、newで生成されていることが分からない部分では使えません
otonoko

2023/01/14 21:52

なるほど、難しいですね... つまり自分のクラスのメンバ変数のアドレスは自分のクラスのメソッド内で確保する必要があるということなのでしょうか?
dameo

2023/01/14 22:13

C++は自由にリスクも負えるので、制約を自分で正確に守れればどう書いてもいいですよ。 ただし、リスクはなるべく増やしたくないので、ある程度は制約を自分で意識しなくていいように設計することが多いです。今回の例で一番簡単なのはBの中に実体を持つ形だと思います。 生成・消滅が連動するので、リスクはありません。 #include <iostream> class A{ public: A(int value): mValue(value) {} // 初期値指定追加 A(const A& org): mValue(org.mValue) {} // コピーコンストラクタ追加 const int& GetValue()const{return mValue;} private: int mValue; }; class B{ public: B(int value):mA(value){} // Aのコンストラクタ引数を指定する void DoSomething(){ std::cout << mA.GetValue() << std::endl; } private: const A mA; // 参照ではなく実体を持つ }; int main(){ B b(1); // 普通のコピーコンストラクタ(参照ではない) b.DoSomething(); return 0; // b.mAはbの破棄中に破棄される。aは別インスタンスで、ここで破棄される。 } ただし、設計上はAとBが密な関係になっており、また生成/破棄タイミングも固定になるので、それをが設計上好ましくないかもしれません。
dameo

2023/01/14 23:40

返事がないようなので少し勝手に進めてしまいます。 以上から、C++の参照はメンバ変数として使用するとちょっと使いにくいのです。かといって実体を持たせるとリスクは減らせるものの自由度が低くて使いにくい。 その辺をラフに書けるのがポインタです。しかし生ポインタを使うと危険が多すぎる。 そこで話によく聞くスマートポインタの出番になります。 #include <iostream> #include <memory> class A{ public: A(int value): mValue(value) {} A(const A& org): mValue(org.mValue) {} ~A() {std::cout << "~A()" << std::endl;} // デストラクタ追加 const int& GetValue()const{return mValue;} private: int mValue; }; class B{ public: B(std::shared_ptr<A> value):mA(value){} void DoSomething(){ std::cout << mA->GetValue() << std::endl; } private: std::shared_ptr<A> mA; // スマートポインタ(共有)を持つ }; int main(){ std::shared_ptr<A> a = std::make_shared<A>(1); B b(a); b.DoSomething(); return 0; // スマートポインタによりAのデストラクタは正しく1度だけ呼ばれる } 形としては最初の参照の形に近いもののポインタ版ですが、リスクが低くなります。 機能も増え安全ですが、構造が冗長なのとスレッドセーフな分生ポインタより遅くなります。 また循環参照には対応できないので注意です。 親戚にunique_ptrとweak_ptrがいます。 詳しくは↓ https://cpprefjp.github.io/reference/memory/shared_ptr.html
thkana

2023/01/15 01:08

そもそもの話として、最初のhoshi-takanoriさんの例えを借りるなら、 相手に直接財布を探らせるのではなく、「いくら持っているか聞いてよ、答えるから」と言ってやる話で、getterは正直に財布の中身を調べてそのまま教えるということでしょ? それが「一般によくない」のでしょうか? もちろん、大金があることを知られるとタカられると思うなら「補正」した金額を言うようにする必要があるかも知れませんがそういうのは一般の話ではありませんし、そもそも友達に所持金額を教える義理はない、というならgetterがどうとかでなくクラスの責務の話になるわけです。 なんでそうなっているのか知りませんが、getterが絶対悪にされちゃってるから話がおかしくなるのではないでしょうか? > 一般的にgetterの公開はよくないとされている これの出どころを求めます。もちろん、メンバ変数全てそれぞれにgetterが用意されているような設計はナニカおかしいだろ? と言いたくなりますが、getterが「一般によくない」というのはどういう根拠でしょう? ゲームで自機を追跡する敵に自分の位置を知らせる(問い合わせに答える)のはなんらおかしな話ではないと思います。
dodox86

2023/01/15 02:45

> 一般的にgetterの公開はよくないとされている 聞いたことが無い文句ですね。何の為にgetterがあるのか。出処を求めたいところです。似たような話があったとして、前後の文脈を無視した引用なのではないでしょうか。
dameo

2023/01/15 03:32

はじめそこ(getterの風聞の出処について)を聞こうかと思ってたのですが、hoshi-takanoriさんの切り口はまずメンバの直参照でした。確かにgetterどころの騒ぎではないと思います。 質問者さんの考え方によっては本筋の話ではないかもしれませんが、getterの風聞などについて私が無理に想定したのは以下のような考え方です。 (1) A,Bを直に参照させると循環参照になりやすく、コールバック地獄などの問題が起こると思った (2) A,Bを直に参照させると関係が密になり、好ましくないと思った (3) A,Bを直に参照させると、(想定している)生成パターンなどに問題があると思った 個人的にはgetterに悪い風聞があるとは思っていませんが、まあそんなに目くじら立てなくてもいいかと思いますよ。
otonoko

2023/01/15 04:34

返答遅くなってしまう申し訳ないです。dameoさんたくさんの貴重な知識ありがとうございます。とても参考になりました。 またほかの方もgetterについての考え方を教えていただきありがとうございます。 私がgetterが良くないのではないかと考えていたソースですが。 一つ目はゲームプログラマのためのコーディング技術127pより 「全てのメンバ変数をprivateにしても、メンバ変数にアクセスするためのgetter,setterを持たせてしまうとカプセル化は崩壊してしまうます。クラスの内部構造を外部に漏らしてしまうからです。」 という記述 二つ目は以下のサイトです https://qiita.com/katolisa/items/6cfd1a2a87058678d646 [求めるな、命じよ]という言葉も度々耳にします。 以上がgetterをあまり公開したくなかった理由です。 正直getterやsetterを普通に公開している書籍もあれば、使うべきではないと考えている、コーディングの本など様々なものがあり結局どれが正解なのかが分からない状況です。 ですが公開しなければ少なくともそれが直接問題にはならないだろうと考えていたため、「getterはよくないと一般的にはされている」という、主語の大きい意見を持ってしまったのかもしれません。申し訳ないです。
dameo

2023/01/15 06:00

"Tell-Don't-ask"の話だったんですね。ある程度以上の規模になると、小さなクラスがこの辺からはこう見えるけど、あちらからはああ見える、など見え方が結構変わります。機能ごとにそういう実装をそのクラス1つに詰め込むと、移譲しようが何しようが結構複雑に見えてしまったりしますよね。そんなとき、私のようなアホはもう(ほぼ)データだけにしよう!とgetter/setterだけのクラスを1つ作り、それを例えば○○Managerとか一般的過ぎて何するのか分からない名前のクラスに渡して処理してもらうことにしちゃいます。すると元のクラスは1つのデータクラスと複数の機能クラス(これは複数のデータクラスを処理する形になる)の一部に分離しちゃったりするわけですが、こうなると"Tell-Don't-ask"どころじゃなくなってしまいます。 ただ実感としてはメンテナンスは分離した方が簡単だし、同じ形にもしやすくなり、フレームにも綺麗に乗りやすい印象です。クラスの構造は簡単には変えられませんが、メソッドの中身は比較的変えやすいですからね。近年クラス自体を持たない言語が多いのも、この辺が理由なのかなぁと個人的には思っています。 何はともあれご説明ありがとうございます。おっしゃりたい内容は分かりました。個人的な結論は「ケースバイケースすぎて回答できない」という感じです。すみません。
otonoko

2023/01/15 10:48

回答ありがとうございました。抱えていた問題の解決以上のものを知ることが出来ました。 dameoさんのコメントが一番ためになりましたが回答の場所で答えていなかったため、頂いた回答をベストアンサーとさせていただきます。
fana

2023/01/16 01:49 編集

> プレイヤーキャラを追尾する敵キャラクターを作りたい 作っている物的に, 「敵」の行動決定が「プレイヤーキャラ」の情報に基づくことは 普通によくあること であろうと思うので, そしたら,「敵キャラクター」の行動決定を行うメソッドの引数として,「プレイヤーキャラ」のconst参照を渡す,くらいで良いのではないでしょうか. (ずっと参照を握っておくのではなくて.処理に必要なものは引数で渡す,というのが最もシンプルな形であろうし,寿命がどうの…いう問題も生じないだろうし.)
guest

回答1

0

ベストアンサー

"Tell. Don't ask." という格言があります。 状態を尋ねてそれに応じて処理するのではなく、処理せよという指示を出すような構成が好ましいという考え方です。 可能であれば他のオブジェクトへの問い合わせというものが発生しないのが一番よいのです。

とはいえ当然ながら常にそうできるわけではありませんし、個々の場面でどういう選択をすべきなのかは状況によるので一概には言えません。 たとえば A が完全にデータの保持のためだけのクラス (たとえば標準ライブラリで言えば std::pair のような) であるならゲッタを定義する必要すらなくデータメンバを public にしても問題ないでしょう。

リストや木のようなデータ構造とノードのような密接な関係があって他に使いまわしをする可能性がないならフレンドクラスにするのが一般的な構成だと思います。

オブジェクト同士の結合がもっと緩い場合ならゲッタを介する設計が特段に悪いとは感じません。

投稿2023/01/15 05:09

SaitoAtsushi

総合スコア5444

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問