数千行のコードに圧倒されましたか?
ディレクトリ内のクラスごとに1セットのヘッダー/ソースファイルがあると、やり過ぎに思えるかもしれません。そして、クラスの数が100または1000に近づくと、恐ろしいことさえあります。
しかし、「すべてをまとめよう」という哲学に従って情報源を試してみた結果、ファイルを作成した人だけが内部で失われないことを望んでいるという結論に達しました。IDEを使用している場合でも、20,000行のソースで遊んでいるときは、問題を正確に参照していないものについては心を閉ざしているだけなので、見逃しがちです。
実際の例:これらの千行のソースで定義されたクラス階層は、ひし形の継承に閉じこめられ、一部のメソッドは、まったく同じコードを持つメソッドによって子クラスでオーバーライドされました。これは簡単に見落とされ(20,000行のソースコードを調べたりチェックしたりしたい人はいますか?)、元の方法が変更されたとき(バグ修正)、その効果は例外として普遍的ではありませんでした。
依存関係は循環的になりますか?
テンプレートコードでこの問題が発生しましたが、通常のC++およびCコードでも同様の問題が発生しました。
ソースを構造体/クラスごとに1つのヘッダーに分割すると、次のことが可能になります。
- オブジェクト全体を含める代わりにシンボルの前方宣言を使用できるため、コンパイルを高速化できます
- クラス間に循環依存関係がある(§)(つまり、クラスAにはBへのポインターがあり、BにはAへのポインターがあります)
ソース制御のコードでは、クラスの依存関係により、ヘッダーをコンパイルするためだけに、ファイルをファイルの上下に定期的に移動する可能性があります。異なるバージョンの同じファイルを比較するときに、そのような動きの進化を研究したくありません。
個別のヘッダーを使用すると、コードがよりモジュール化され、コンパイルが高速になり、さまざまなバージョンの差分を介した進化の調査が容易になります。
テンプレートプログラムでは、ヘッダーを2つのファイルに分割する必要がありました。テンプレートクラスの宣言/定義を含む.HPPファイルと、前述のクラスメソッドの定義を含む.INLファイルです。
このすべてのコードを1つの一意のヘッダー内に配置するということは、このファイルの先頭にクラス定義を配置し、最後にメソッド定義を配置することを意味します。
そして、誰かがコードのごく一部だけを必要とし、ワンヘッダーのみのソリューションを使用している場合でも、コンパイルの速度を落とす必要があります。
(§)どのクラスがどのクラスを所有しているかがわかっている場合は、クラス間で循環依存関係を持つことができることに注意してください。これは、shared_ptr循環依存アンチパターンではなく、他のクラスの存在を知っているクラスについての議論です。
最後に一言:ヘッダーは自給自足である必要があります
ただし、複数のヘッダーと複数のソースのソリューションで尊重する必要があることが1つあります。
1つのヘッダーを含める場合、どのヘッダーに関係なく、ソースはクリーンにコンパイルする必要があります。
各ヘッダーは自給自足である必要があります。10,000以上のソースファイルプロジェクトをgrepして、列挙型が1つだけであるために含める必要のある1,000行のヘッダーのシンボルを定義するヘッダーを見つけることで、トレジャーハンティングではなくコードを開発することになっています。
これは、各ヘッダーが使用するすべてのシンボルを定義または前方宣言するか、必要なすべてのヘッダー(および必要なヘッダーのみ)を含めることを意味します。
循環依存に関する質問
アンダースコア-dは尋ねます:
個別のヘッダーを使用すると、循環依存にどのような違いが生じるかを説明できますか?そうは思わない。両方のクラスが同じヘッダーで完全に宣言されている場合でも、もう一方のクラスでハンドルを宣言する前に一方を事前に宣言するだけで、循環依存関係を簡単に作成できます。他のすべては素晴らしい点のようですが、別々のヘッダーが循環依存を促進するという考えはかなり遠いようです
underscore_d、11月13日23:20
AとBの2つのクラステンプレートがあるとします。
クラスA(またはB)の定義にB(またはA)へのポインターがあるとしましょう。また、クラスA(またはB)のメソッドが実際にB(またはA)からメソッドを呼び出すとしましょう。
クラスの定義とそのメソッドの実装の両方に循環依存関係があります。
AとBが通常のクラスであり、AとBのメソッドが.CPPファイルにある場合、問題はありません。前方宣言を使用し、各クラス定義にヘッダーを設定すると、各CPPに両方のHPPが含まれます。
ただし、テンプレートがあるため、実際には上記のパターンを再現する必要がありますが、ヘッダーのみを使用します。
これの意味は:
- 定義ヘッダーA.def.hppおよびB.def.hpp
- 実装ヘッダーA.inl.hppおよびB.inl.hpp
- 便宜上、「ナイーブ」ヘッダーA.hppおよびB.hpp
各ヘッダーには、次の特性があります。
- A.def.hpp(またはB.def.hpp)には、クラスB(またはA)の前方宣言があります。これにより、そのクラスへのポインター/参照を宣言できます。
- A.inl.hpp(またはB.inl.hpp)には、A.def.hppとB.def.hppの両方が含まれます。これにより、A(またはB)のメソッドがクラスB(またはA)を使用できるようになります。 。
- A.hpp(またはB.hpp)には、A.def.hppとA.inl.hpp(またはB.def.hppとB.inl.hpp)の両方が直接含まれます。
- もちろん、すべてのヘッダーは自給自足であり、ヘッダーガードによって保護されている必要があります
ナイーブなユーザーにはA.hppやB.hppが含まれるため、混乱全体が無視されます。
そして、その編成を持つということは、ライブラリ作成者がAとBの間の循環依存を解決しながら、両方のクラスを別々のファイルに保持できることを意味します。スキームを理解すれば、簡単にナビゲートできます。
これはエッジケースであることに注意してください(2つのテンプレートがお互いを知っています)。私はほとんどのコードがそのトリックを必要としないことを期待しています。