C ++コードでクラスの相互依存を解決するにはどうすればよいですか?


10

私のC ++プロジェクトには、2つのクラスとがParticleありContactます。でParticleクラス、Iは、メンバ変数有するstd::vector<Contact> contactsのすべての連絡先含まParticleオブジェクトを、対応するメンバ関数をgetContacts()addContact(Contact cont)。したがって、「Particle.h」には「Contact.h」を含めます。

ではContact、クラス、私はのためのコンストラクタにコードを追加したいContactことが呼び出すParticle::addContact(Contact cont)ように、contacts両方のために更新されてParticleいる間でオブジェクトContactオブジェクトが追加されています。したがって、「Contact.cpp」に「Particle.h」を含める必要があります。

私の質問は、これが許容できる/適切なコーディングプラクティスであるかどうかであり、そうでない場合は、達成しようとしていることを実装するためのより良い方法は何ですか(簡単に言えば、新しい連絡先のたびに特定の粒子の連絡先のリストを自動的に更新します)創造された)。


これらのクラスは、NetworkN個の粒子(std::vector<Particle> particles)とNc個の接触(std::vector<Contact> contacts)を持つクラスによって結合されます。しかし、私は次のような関数を使用できるようにしたかったのです。この場合、クラスにparticles[0].getContacts()そのような関数があっても大丈夫ですParticleか、またはこの目的のためにC ++でより良い関連付け「構造」があります(別のクラスで使用されている2つの関連クラスの) 。


私はこれにどのように取り組んでいるのか、ここで視点のシフトが必要かもしれません。2つのクラスはNetworkクラスオブジェクトによって接続されているため、接続情報をNetworkオブジェクトによって完全に制御するのが一般的なコード/クラス編成ですか(パーティクルオブジェクトはその連絡先を認識してはならず、その結果、getContacts()メンバーを持つべきではありません)関数)。次に、特定の粒子が何に接触しているかを知るために、Networkオブジェクトを介して(たとえばを使用してnetwork.getContacts(Particle particle))その情報を取得する必要があります。

Particleオブジェクトがその知識を持つこと(つまり、NetworkオブジェクトまたはParticleオブジェクトのいずれかを介して、その情報にアクセスするための複数の方法があること)は、あまり一般的ではない(おそらく推奨されない)C ++クラス設計でしょうか)?


4
2017年のcppconからの講演-ヘッダーの3つの層:youtu.be/su9ittf-ozk
Robert Andrzejuk

3
「最高」、「より良い」、「受け入れ可能」などの単語を含む質問は、具体的な評価基準を
Robert Harvey

編集をありがとうございます。ただし、表現を「標準」に変更すると、人気の問題になります。コーディングが何らかの方法で行われる理由があり、人気は技術が「良い」(ある意味「良い」と定義されている)ことを示す場合がありますが、カーゴカルチャーを示す場合もあります。
Robert Harvey

@RobertHarvey最後のセクションで「良い」と「悪い」を削除しました。オブジェクトとNetworkオブジェクトを含むクラスオブジェクトがある場合、私は典型的な(おそらくは好意的/奨励されている)アプローチを求めていると思います。その基本的な知識があれば、プロジェクトに沿って調査/開発されている特定のニーズに合っているかどうかを評価できます。ParticleContact
AnInquiringMind 2017年

@RobertHarvey私はC ++プロジェクトをゼロから完全に書くことができるほど新しいので、「典型的な」「人気のある」ものを学んでも大丈夫だと思います。うまくいけば、ある時点で、別の実装が実際に優れている理由を理解するのに十分な洞察を得られるでしょうが、今のところ、私がこれに完全に骨の折れる方法で取り組んでいないことを確認したいだけです!
AnInquiringMind 2017年

回答:


17

質問には2つの部分があります。

最初の部分は、C ++ヘッダーファイルとソースファイルの構成です。これは、前方宣言とクラス宣言(ヘッダーファイルに置く)とメソッド本体(ソースファイルに置く)を分離することで解決されます。さらに、場合によっては、Pimplイディオム(「実装へのポインター」)を適用して、より難しいケースを解決することができます。ベストプラクティスに従って、共有所有者ポインター(shared_ptr)、単一所有者ポインター(unique_ptr)、および非所有ポインター(生のポインター、つまり「アスタリスク」)を使用します。

2番目の部分は、相互に関連するオブジェクトをグラフの形でモデル化する方法です。DAG ではない一般的なグラフ(有向非循環グラフ)には、ツリーのような所有権を自然に表現する方法がありません。代わりに、ノードと接続はすべて、単一のグラフオブジェクトに属するメタデータです。この場合、ノード接続関係を集約としてモデル化することはできません。ノードは接続を「所有」しません。接続はノードを「所有」しません。代わりに、これらは関連付けであり、ノードと接続の両方がグラフによって「所有」されます。グラフは、ノードと接続を操作するクエリと操作のメソッドを提供します。


ご返信ありがとうございます!実際には、N個のパーティクルとNc個の接点を持つネットワーククラスがあります。しかし、私particles[0].getContacts()は次のような関数を使用できるようにしたかったのですが、最後の段落で、Particleクラスにそのような関数を含めるべきではないこと、または現在の構造は本質的に関連/関連付けされているので問題ないことを示唆していますNetworkか?この場合、C ++にはより良い関連「構造」がありますか?
AnInquiringMind 2017年

1
一般的に、ネットワークはオブジェクト間の関係について知る責任があります。たとえば、隣接リストを使用する場合、パーティクルにnetwork.particle[p]network.contacts[p]その連絡先のインデックスに対応するものがあります。それ以外の場合、ネットワークとパーティクルはどちらも何らかの方法で同じ情報を追跡しています。
役に立たない

@役に立たないええ、それは私が進む方法がわからないところです。つまり、Particleオブジェクトはその連絡先を認識してはならない(getContacts()つまり、メンバー関数を持たない)必要があり、その情報はNetworkオブジェクト内からのみ取得する必要があるということですか。Particleオブジェクトがその知識を持っている(つまり、その情報にアクセスするための複数の方法がある- NetworkオブジェクトまたはParticleオブジェクトのどちらか、どちらか便利だと思われる)のは、C ++クラスの設計として不適切でしょうか?後者は私にはより理にかなっているようですが、多分私はこれに対する私の見方を変える必要があるでしょう。
AnInquiringMind 2017年

1
@PhysicsCodingEnthusiast:s Particleについて知っていることの問題は、その関係を表す特定の方法に結びつくことです。3つのクラスすべてが同意する必要がある場合があります。代わりに、それが知っているか気になっている唯一のクラスである場合、別の表現の方が優れていると判断した場合に変更する必要があるクラスは1つだけです。ContactNetworkNetwork
cHao 2017年

@cHaoわかりました、これは理にかなっています。したがってParticleContact完全に分離する必要があり、それらの間の関連付けはNetworkオブジェクトによって定義されます。念のために言うと、これは(おそらく)@rwongが書いたときに「ノードと接続の両方がグラフによって「所有されている」ことを意味します。グラフは、ノードと接続を操作するクエリと操作のメソッドを提供します。」 、 正しい?
AnInquiringMind 2017年

5

正解ですが、同じ接触オブジェクトは複数のパーティクルオブジェクトに属しています。これは、2つ以上のパーティクル間の何らかの物理的接触を表すためですよね?

だから私が疑問に思う最初のものは、なぜParticleメンバー変数があるのstd::vector<Contact>ですか?代わりにa std::vector<Contact*>またはa std::vector<std::shared_ptr<Contact> >にする必要があります。addContact次に、addContact(Contact *cont)またはaddContact(std::shared_ptr<Contact> cont)代わりに別の署名が必要です。

これにより、「Particle.h」に「Contact.h」をインクルードする必要がなくなり、「Particle.h」に前方宣言がありclass Contact、「Particle.cpp」に「Contact.h」をインクルードするだけで十分です。

次に、コンストラクタについての質問。あなたはのようなものが欲しい

 Contact(Particle &p1, Particle &p2)
 {
      p1.addContact(this);
      p2.addContact(this);
 }

正しい?この設計は問題ありませんが、接触オブジェクトを作成する必要がある時点で、プログラムが関連するパーティクルを常に認識している必要があります。

std::vector<Contact*>ルートに行く場合は、Contactオブジェクトの存続期間と所有権についていくらか考えなければならないことに注意してください。連絡先を「所有」するパーティクルはありません。連絡先は、関連Particleする両方のオブジェクトが破壊された場合にのみ削除する必要があります。std::shared_ptr<Contact>代わりにを使用すると、この問題が自動的に解決されます。または、「周囲のコンテキスト」オブジェクトにパーティクルと連絡先(@rwongによって提案されたような)の所有権を取得させ、その存続期間を管理させます。


addContact(const std::shared_ptr<Contact> &cont)以上のメリットがわかりませんaddContact(std::shared_ptr<Contact> cont)か?
Caleth 2017年

@Caleth:これはここで議論されました:stackoverflow.com/questions/3310737/…-「const」はここではそれほど重要ではありませんが、参照(および値によるスカラー)でオブジェクトを渡すことはC ++の標準イディオムです。
Doc Brown、

2
それらの答えの多くは、前moveパラダイムからのもののようです
Caleth

@Caleth:わかりました、すべてのニッパーを幸せに保つために、私は私の答えのこの非常に重要ではない部分を変更しました。
Doc Brown、

1
@PhysicsCodingEnthusiast:いいえ、このことについて一番あるparticle1.getContacts()particle2.getContacts()同じ送達Contactの間の物理的接触を表すオブジェクトをparticle1し、particle22つの異なるオブジェクトを、としません。もちろん、Contact同じ物理的接触を表す2つのオブジェクトが同時に使用できる場合でも問題にならない方法でシステムを設計することもできます。これはContact不変にすることを含みますが、これがあなたが望むものであることを確信していますか?
Doc Brown

0

はい、あなたが説明することは、すべてのContactインスタンスがの連絡先のリストに含まれることを保証する非常に許容できる方法ですParticle


ご回答ありがとうございます。相互に依存するクラスのペアを避けることをお勧めします(たとえば、MS Joshiによる "C ++ Design Patterns and Derivatives Pricing"で)いくつかの提案を読みましたが、どうやらこれは必ずしも正しいとは限りません。好奇心から、おそらく相互依存を必要とせずにこの自動更新を実装する別の方法はありますか?
AnInquiringMind 2017年

4
@PhysicsCodingEnthusiast:相互に依存するクラスがあると、あらゆる種類の問題が発生するため、それらを回避する必要があります。ただし、2つのクラスが互いに非常に密接に関連しているため、クラス間の相互依存を削除すると、相互依存自体よりも多くの問題が発生する場合があります。
Bart van Ingen Schenau 2017年

0

あなたがしたことは正しいです。

別の方法...すべてContactがリストにあることを確認することを目的とする場合、次のことができます。

  • Contact(プライベートコンストラクタ)の作成をブロックします。
  • 前方宣言Particleクラス、
  • メイクParticleクラスの友人Contact
  • Particle作成するファクトリメソッドを作成しますContact

そして、あなたは含める必要はありませんparticle.hcontact


ご返信ありがとうございます!これは、これを実装する便利な方法のようです。ただ、Networkクラスに関する最初の質問を編集して、提案された構造を変更するのですか、それとも同じですか?
AnInquiringMind 2017年

あなたがあなたの質問を更新した後、それは範囲を変えています。...これで、以前は技術的な問題でしたが、アプリケーションのアーキテクチャについて尋ねています。
Robert Andrzejuk

0

考えられるもう1つのオプションは、パーティクル参照を受け入れるContactコンストラクターを作成することです。これにより、Contactはを実装するコンテナに自分自身を追加できますaddContact(Contact)

template<class Container>
Contact(/*parameters*/, Container& container)
{
  container.addContact(*this);
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.