【C++】相互参照とは?メリット・デメリットを考える

このQ&Aのポイント
  • 相互参照による問題点とは?
  • 相互参照を避けるメリットとは?
  • 強結合を避けながら相互参照を実現する方法は?
回答を見る
  • ベストアンサー

【C++】相互参照

以下のような相互参照は、 感覚的には、なるべくならしない方が良いと感じるのですが、 どのようなメリット・デメリットがあるでしょうか? class A B* m_b class B A* m_a ■少なくとも、多くのクラスによって構成された巨大なDLLが、  相互参照ばかりで作られると、  (1)クラスAのポインタ型のメンバーが、クラスB内で値が変わりうる、   また、クラスBを、クラスCで参照していたら、そこでも値が変わりうる。   さらにクラスCを、クラスDで参照していたら、(略)となり、   処理・メンバー値の変更の影響の把握が困難になる。  (2)lib/objの作成順のミスや管理、  (3)いずれかのクラスのリコンパイルが発生した時、   リリース物のリコンパイルが発生しまくる。  ということがあり、良くないと考えています。 - - - - - - - - - - - - - - - 相互にインタフェースし合う項目を、メンバーとして宣言しつつ、 そのGetter、Setterを用意すれば、強結合にせずに作れるという認識ですが合っていますか? class A B* m_b int x1、y1、z1   x1、y1、z1のGetter、Setter class B Privateなメンバーx、y、z int x2、y2、z2   x2、y2、z2のGetter、Setter .

質問者が選んだベストアンサー

  • ベストアンサー
  • qwertfk
  • ベストアンサー率67% (55/81)
回答No.2

必然性が無い場合でも相互参照にしたほうが便利になる、ということがよくあります。 たとえば先ほどの大学と学生の話で、 大学の所属学生一覧は必要なのでとりあえず次のように大学を作ったとします。 class College { public:  vector<Student*> students; }; それで、各学生の所属大学内での何かの順位を取得する機能が必要だったとして、たとえば A:student->GetRankInCollege(); のような設計にするにはstudentの所属大学の情報を得るために Student内にCollegeクラスへの参照が必要です。 ですが、相互参照が嫌という判断で、これを B:college->GetRank(student); のように実装したり、 C:GetRankOfStudent(college, student); のように実装したりすることもできます。 ですが、B,Cの場合、これらの機能を利用するプログラマからすれば、 たとえば、「現在のユーザのランクをログインページに表示する」 といったことをしたい場合にいちいち ・現在ログインしている学生 ・その学生が所属している大学 の2つを管理しなければならないという手間が発生します。 上記例の場合、単純にあらゆる箇所でcollege, studentのペアをちゃんと管理する、という労力が あまり大きくないと判断すれば無駄に相互参照にする必要はありません。 ですが、そのあたりの管理を単純にしたいと思った場合に、「相互参照になるからこのような設計にはしない」 のように考えるほどのリスクは無いと思います。

TeferiMage
質問者

お礼

確かに。 わかりやすい説明、ありがとうございます! 納得しました!

その他の回答 (1)

  • qwertfk
  • ベストアンサー率67% (55/81)
回答No.1

人によって意見が分かれるところかもしれませんが、 相互参照自体は若干注意が必要ではあるが特に問題がある設計ではないと思います。 たとえば、 ・フォルダは自分が持っているファイル、フォルダを知っている ・ファイルは自分が保存されているフォルダを知っている とか ・学校は所属する学生を知っている ・学生は通学している学校を知っている という感じで現実に相互参照があリますので、それをコードにしたときに相互参照する のは自然だと思います。 質問に書かれている問題点というのは、要するに相互参照ではなく、結合度の問題です。 各モジュールの結合度が高いとまさに質問に書かれているような問題が発生するので良くありませんが、 それは相互参照でなくとも発生する一般的な問題です。 ちゃんと設計をして、低結合、工凝集なモデルを作る、というのが教科書的ではありますが一般的対策です。 これらの問題にgetter,setterは関係ありません。 オブジェクト指向言語であれば、抽象クラスやインタフェースを使って 依存度を下げるというのが定石です。 たとえば、 // 乗り物(公共交通機関的なもので) class Vehicle { // 今乗っている乗客 vector<Passenger*> passengers; // 運賃 virtual int price(); }; // 乗客 class Passenger { // 今乗っている乗り物 Vehicle* vehicle; }; となっていると相互参照ですが、これをベースに class Bus : public Vehicle class Taxi : public Vehicle という風に実装すると、乗客から見ると乗り物の詳細を知らなくても一般的な 交通機関のインタフェースを知っていれば、あらゆる乗り物に大体同じ方法で乗れる というような実装ができます。

TeferiMage
質問者

お礼

お返事遅れてしまいすみません! ありがとうございました!

TeferiMage
質問者

補足

なるほどです。 結合度・凝集度が大事ですね。 確かに 学生が学校を知っていて、 学校も学生を知っている例のときのなど、 (学生側からは、「授業に出る」のメソッドで、「知識」のメンバー変数が更新され、 学校側こらは、「教える」ことで、「資金」のメンバーが更新される といったことは自然に感じます) ただ、そういうとき、それぞれのメンバー変数に、 お互いのポインタを持ち合う必要があるかというと、 それぞれのクラスが、メソッドを「static」かつ「public」として提供すれば良いと思うのです。

関連するQ&A

  • サブクラスでセッターを呼び出す

    java初心者です。 以下のようなソースコードで、コンパイルしたらエラーが出ました。 サブクラスのsetterのところでエラーが出たんですが、 サブクラスでスーパークラスのメソッドは呼び出せないんでしょうか? それとも他に間違いがあるんでしょうか? class rensyu { private int x; rensyu(){ x=this.x; } int getX(){ return x; } void setX(int m){ this.x = m; } } class rensyu2 extends rensyu{ private rensyu[] A; rensyu2(){ A = new rensyu[100]; } int getter(int i){ return A[i].getX(); } void setter(int x,int i){ A[i].setX(x); } } class rensyuTester{ public static void main(String[] args) { rensyu2 A = new rensyu2(); A.setter(1,0); A.setter(2,1); System.out.println(A.getter(0) +" "+ A.getter(1)); } }

    • ベストアンサー
    • Java
  • 派生クラスのメンバを基底クラスの参照に代入(C++

    文末のコードのように、 基底クラスで、派生クラスのメンバの参照を持つのはまずいでしょうか。 (classではなくstructにしているのは質問上でのpublic:の省略のためだけです) 初期化順序的には、基底クラスの参照先は、 基底クラスのコンストラクタが走る時点で初期化されていないので、 コンストラクタ内で参照に対して何かしようとすると問題になると思っています。 基底クラスのコンストラクタ内で派生クラスメンバの参照に対して何かしなければ、 参照は有効で、派生クラスのコンストラクタ実行後であれば 問題なく動くと思ってよいでしょうか。 struct A { int& m_ref; A(int& ref) : m_ref(ref) { } }; struct B : public A { int m_obj; B() : A(m_obj) { } };

  • 相互参照するクラス、俺こんな日々を暮らすイェー

    C++初心者です。相互参照するクラス(構造体?)の作り方で悩んでいます。一般的な相互参照の解決法は沢山のサイトに載っているのですが・・・ よくある相互参照と解決法 --------------------- struct Test2; struct Test1{ Test2 *t2; }; struct Test2{ Test1 *t1; }; --------------------- これはtest1の中でtest2を使いたい時にその前に名前だけとりあえず宣言しておけばOK(wave)、というもの。(間違ってたらご指摘お願いします。) 自分が現在どうすればいいか途方に暮れている相互参照 --------------------- struct Test2; struct Test1{ int n; Test2 *t2; void Func1(Test2 *t2){ this->t2->m = 777; } }; struct Test2{ int m; Test1 *t1; void Func2(Test1 *t1){ this->t1->n = 777; } }; --------------------- ここでは各メンバ関数内でお互いのメンバを参照します。このときのエラーは、 「Test2のメンバは宣言されていないので、mはTest2のメンバではありません。」 です。これは this->t2->m = 777; の行に対するエラーです。 struct Test2; の行でメンバまで宣言することができません。 どうすれば「Test2の中にmというメンバがあるって後で教えてあげるからちょっとまって」とコンパイラに伝えられるのか、教えてください。お願いします!

  • 数学(ベクトル)の問題

    http://okwave.jp/qa/q8022847.html のNO.7の回答より、 さらに途中式を書いたのですが、 L^2 = m(t-n(s))^2-(a1^2+b1^2+c1^2 ) {(c1(z2-z1)+b1(y2-y1)+a1(x2-x1)+(a1a2+b1b2+c1c2)s)/(a1^2+b1^2+c1^2 )}^2+(a2^2+b2^2+c2^2 ){(s^2 )+2s{c2(z2-z1)+b2(y2-y1)+a2(x2-x1)}/{(a2^2+b2^2+c2^2 )} }+{(x2-x1)^2 }+{(y2-y1)^2 }+{(z2-z1)^2 } n(s)={c1(z2-z1)+b1(y2-y1)+a1(x2-x1)+(a1a2+b1b2+c1c2)s}/(a1^2+b1^2+c1^2 ) = m(t-n(s))^2-{c1(z2-z1)+b1(y2-y1)+a1(x2-x1)+(a1a2+b1b2+c1c2)s}^2/(a1^2+b1^2+c1^2 )+(a2^2+b2^2+c2^2 ){(s^2 )+2s{c2(z2-z1)+b2(y2-y1)+a2(x2-x1)}/{(a2^2+b2^2+c2^2 )} }+{(x2-x1)^2 }+{(y2-y1)^2 }+{(z2-z1)^2 } = m(t-n(s))^2-{c1(z2-z1)+b1(y2-y1)+a1(x2-x1)+(a1a2+b1b2+c1c2)s}^2/(a1^2+b1^2+c1^2 )+(a2^2+b2^2+c2^2 )(s^2 )+2s{c2(z2-z1)+b2(y2-y1)+a2(x2-x1)}+{(x2-x1)^2 }+{(y2-y1)^2 }+{(z2-z1)^2 } ところで {c1(z2-z1)+b1(y2-y1)+a1(x2-x1)+(a1a2+b1b2+c1c2)s}^2 = {c1(z2-z1)+b1(y2-y1)+a1(x2-x1)}^2 +2{c1(z2-z1)+b1(y2-y1)+a1(x2-x1)}(a1a2+b1b2+c1c2)s +{(a1a2+b1b2+c1c2)s}^2 = {c1(z2-z1)+b1(y2-y1)+a1(x2-x1)}^2 +2{c1(z2-z1)+b1(y2-y1)+a1(x2-x1)}(a1a2+b1b2+c1c2)s +(a1a2+b1b2+c1c2)^2 (*s)^2 これより、 L^2= m(t-n(s))^2+s^2 {(a2^2+b2^2+c2^2 )-(a1a2+b1b2+c1c2)^2/(a1^2+b1^2+c1^2 )} +s[2{(c2(z2-z1)+b2(y2-y1)+a2(x2-x1))-{c1(z2-z1)+b1(y2-y1)+a1(x2-x1)}(a1a2+b1b2+c1c2)s/(a1^2+b1^2+c1^2 )}] +{(x2-x1)^2 }+{(y2-y1)^2 }+{(z2-z1)^2 }-{c1(z2-z1)+b1(y2-y1)+a1(x2-x1)}^2/(a1^2+b1^2+c1^2 ) 簡単、 L^2 =m(t-n(s))^2+ps^2+p1s+p2 =m(t-n(s))^2+p(s^2+p1s/p)+p2 =m(t-n(s))^2+p(s^2+p1s/p+(p1/p)^2-(p1/p)^2 )+p2 =m(t-n(s))^2+p(s^2+p1s/p+(p1/p)^2 )-(p1)^2/p+p2 まで、計算したのですが(間違っていたら申し訳ありません)、 ここから、どのように q=-p1/2p が導出できるのかがわからないです。 (rは導出できました。) 数式だらけで分かりづらいと思いますが、計算ミスを指摘しつつ、導出過程も分かりやすくお願いします。

  • C++のfriend classについて

    C++のfriend classについて C++の初心者です。 C++でのfriend classは基本的に、全プロパティに対するsetter,getterを持たせて 集約、コンポジションにより代替が可能だと思うのですが、今勉強の為に読解して いるソースではsetter,getterの実装がなされているクラスの使用に対しても friend 宣言が多発していて中途半端なルールになっていると感じてしまいます。 friend 宣言によって別途実現可能になる事や、パフォーマンス的に有利(微々たる 物なはず)な事ってあるのでしょうか?

  • 参照による呼び出し

    参照による呼び出しで3つの整数を大きい順に並び替えるという プログラムで、 #include<stdio.h> void change(int *x,int *y,int *z);/       main(){ int a,b,c; scanf("%d %d %d",&a,&b,&c); printf("入力データ:a=%d,b=%d,c=%d\n",a,b,c); change(&a,&b,&c); printf("入れ替え後:a=%d,b=%d,c=%d\n",a,b,c); return(0); } void change(int *x,int *y,int *z){ if(*x>*y){ *a=*x; *c=*y; } else{ *a=*y; *c=*x; } if(*z>*a){ *b=*a; *a=*z; } else if(*z>*c){ *b=*z; } else{ *b=*c; *c=*z; } } のように作ったのですが、zの値が一度も使われていないとエラーが 出てしまいます。どこをどう直せばいいか教えてください。 お願いします。

  • c++でのヘッダーファイルの循環参照

     c++言語においての質問です.  A, Bという二つのクラスを作ったとします.  宣言は.h,実装は.cppで行っています. 【A.h】 class A{ //内容 public: B ConvertB(); }; 【B.h】 class B{ //内容 public: A ConvertA(); };  上のように,クラスAではクラスBを返すメソッド. クラスBではクラスAを返すメソッドを実装したいとします.  しかし,単純に 【A.h】に #include "B.h" 【B.h】に #include "A.h" などとすると当然循環参照になってエラーになりますよね? ...とは言っても, 【A.cpp】や【B.cpp】にインクルードした所で, ヘッダ側でエラーが起きてしまいます.  そこで,私の場合は... 【A.h】の先頭に class B{ConvertAで用いるメソッド}; 【B.h】の先頭に class A{ConvertBで用いるメソッド};  というようにする事でなんとかエラーを避けています.    ...しかし,この方法ではAやBのクラスの定義を修正する場合に, 2つのファイルのヘッダを書き換えなければならなくなります. A, B, C, D...などとクラスが増えていくと, 一つのヘッダーファイルに4つも5つもクラスの定義を書かなければなりませんし, クラスのメソッドの定義を一つ変えようとしただけでも, 複数のヘッダの内容をいじらないといけません.  非常に読みにくいコードになってしまうのです.  そこで,もう少しスマートに実現する方法は無いでしょうか?  JavaやC#を使えば簡単に解決するのですが...ライブラリの関係でC++を使わなければ ならないのです.  もしくは,このような相互変換?のクラスを作る場合はみなさんはどのようにして ヘッダーファイルの循環参照を避けているのでしょうか?  例えば様々な形式の色空間のクラスなどだったら... RGBクラス YCrCbクラス HSVクラス ...など  このようなクラスを作った後で,RGBをYCrCbに変換出来るようにしたり, その逆へも変換出来るようにしたいのです.  それともこのような変換が出来るクラスを作る事は間違っているのでしょうか?    よろしくお願いします.

  • Wordの相互参照機能について

    Wordの図表番号機能を用いて挿入した数式の番号を相互参照により本文中に埋め込もうとしています。しかし、数式番号の前の式 (ex, x+y=0 (1-14)) までもが表示されてしまいます。 (誤)式 x+y=0 (1-14)を微分すると… (正)式(1-14)を微分すると… どうしても(誤)のようになってしまうのですが、どうにかならないでしょうか。

  • 3次元上の三角形内の任意点の高さを求める公式の導き方

    3次元上の三角形内の任意点の高さを求める公式の導き方 3次元上に三角形(平面)があり各頂点(X1,Y1,Z1),(X2,Y2,Z2),(X3,Y3,Z3)の座標が明確であるとき その面上にある任意点(X,Y)のZを求めたいのですが・・・。 公式は発見できたのですが、どのようにしてこの公式が導き出されたのかわかりません。 Z=a(X-X1)+b(Y-Y1)+Z1 a=[(Z2-Z1)(Y3-Y1)-(Y2-Y1)(Z3-Z1)]/[(X2-X1)(Y3-Y1)-(Y2-Y1)(X3-X1)] b=[(X2-X1)(Z3-Z1)-(Z2-Z1)(X3-X1)]/[(X2-X1)(Y3-Y1)-(Y2-Y1)(X3-X1)] 頭の良くないので・・・・ 導き出し方をできるだけわかりやすく教えていただけないでしょうか。

  • 相互インダクタンスの基本的な問題

    ある参考書片手に、電気工学を勉強しています。 その中で、相互インダクタンスの問題があるのですが、どうも腑に落ちないところがあり、お聞きしたく思います。 問題は下記のとおりです。 コイルA、コイルBがありコイルAは400回巻き、コイルBは600回巻き。 コイルAに電流を流すと、コイルAに磁束が鎖交し、その磁束はコイルBにも鎖交します。このとき、Aに5[A]の電流を流して、コイルAには5x10^-3[Wb]の磁束が鎖交し、Bには2x10^-3[Wb]が鎖交します。 コイルAの自己インダクタンスLa と相互インダクタンスMを求めよ。 私の答え NaΦ=LaIaの式から La = (NaΦ)÷Iaとして La = (400 x 5x10^-3)÷5 = 0.4[H] 従ってコイルAの自己インダクタンスは 0.4[H] 次に、M = (NbΦa)÷Iaなので M = (600 x 5x10^-3)÷5 = 0.6[H] としました。 ここまで記述して、コイルBに鎖交した磁束は計算に入ってません。 参考書の答えは、 M = (NbΦb)÷Ia として計算されています。つまり、 M = (600 x 2x10^-3)÷5 = 0.24[H] となっています。 しかし、この参考書の公式説明の中で、 M = (NbΦb)÷Ia この式は一度も出てきていません。 もっといえば、 M = (NbΦa)÷Ia という式があるのであれば、相互インダクタンスMはコイルA側の磁束ΦaとコイルAに流れる電流Ia、コイルBの巻き数がわかれば求まってしまいます。 ちょっと混乱してますが、 M = (NbΦa)÷Ia の式は、コイルBにもコイルAで発生した磁束がそのまま鎖交する場合にのみ有効っていう解釈でしょうか? とすれば、 M = (NbΦb)÷Ia とする理由もわからなくは無いのですが、今度はこの式のIaというコイルAに流れる電流を割る意味がよくわからなくなってきます。 識者の方の出来ればわかりやすい解説がいただけるとありがたく思います。 よろしくお願いします。

専門家に質問してみよう