私が取り組んでいるC++プロジェクトのインクルードをクリーンアップしています。特定のファイルで直接使用されるすべてのヘッダーを明示的に含める必要があるのか、それとも最小限のものだけを含める必要があるのか疑問に思っています。
次に例を示します。Entity.hpp
:
#include "RenderObject.hpp"
#include "Texture.hpp"
struct Entity {
Texture texture;
RenderObject render();
}
(RenderObject
の前方宣言はオプションではないと仮定しましょう。)
これで、RenderObject.hpp
にTexture.hpp
が含まれていることがわかります。RenderObject
にはTexture
メンバーがあるため、それでも、Texture.hpp
にEntity.hpp
を明示的に含めています。これは、RenderObject.hpp
に含まれていることに依存するのが適切かどうかわからないためです。
だから:それは良い習慣ですか?
.cppファイルで使用されているオブジェクトを定義するすべてのヘッダーを、それらのファイルの内容に関する知識に関係なく、常にそのファイルに含める必要があります。ヘッダーを複数回インクルードしても問題にならないように、すべてのヘッダーファイルにガードを含める必要があります。
理由:
Texture
オブジェクトを処理していることがわかります。RenderObject.hpp
は実際には必要ありませんTexture.hpp
自体。当然の結果として、そのファイルで明示的に必要でない限り、ヘッダーを別のヘッダーに含めないでください。
一般的な経験則は次のとおりです。使用するものを含めます。オブジェクトを直接使用する場合は、そのヘッダーファイルを直接インクルードします。 Bを使用するオブジェクトAを使用し、Bを自分で使用しない場合は、A.hのみを含めます。
また、このトピックについては、実際にヘッダーで必要な場合にのみ、ヘッダーファイルに他のヘッダーファイルを含める必要があります。 .cppにのみ必要な場合は、そこにのみ含めます。これはパブリック依存関係とプライベート依存関係の違いであり、クラスのユーザーが実際に必要としないヘッダーをドラッグすることを防ぎます。
特定のファイルで直接使用されるすべてのヘッダーを明示的に含める必要があるかどうか疑問に思っています
はい
他のヘッダーがいつ変更されるかはわかりません。各翻訳単位に、その翻訳単位が必要とすることがわかっているヘッダーを含めることは、世界中で理にかなっています。
ダブルインクルージョンが有害でないことを確認するためのヘッダーガードがあります。
これについては意見が異なりますが、すべてのファイル(c/cppソースファイルでもh/hppヘッダーファイルでも)を独自にコンパイルまたは分析できるはずだと思います。
そのため、すべてのファイルに必要なヘッダーファイルをすべて含める必要があります。1つのヘッダーファイルがすでに含まれているとは限りません。
ヘッダーファイルを追加する必要があり、直接インクルードせずに他の場所で定義されているアイテムを使用していることがわかった場合は、本当に大変です。
反対に、必要のないファイルを#includeしても、(原則として)重要ではありません...
個人的なスタイルのポイントとして、私は#includeファイルをアルファベット順に配置し、システムとアプリケーションに分割します-これは、「自己完結的で完全に一貫した」メッセージを強化するのに役立ちます。
別のケースも考えられます。A.h、B.hがあり、C.cpp、B.hにはA.hが含まれます。
c.cppでは、次のように書くことができます
#include "B.h"
#include "A.h" // < this can be optional as B.h already has all the stuff in A.h
ここで#include "A.h"を記述しないとどうなりますか? C.cppでは、AとBの両方(クラスなど)が使用されます。その後、C.cppコードを変更し、B関連のものを削除しますが、B.hはそこに含めたままにします。
A.hとB.hの両方を含め、この時点で、不要なインクルードを検出するツールは、B.hインクルードが不要になったことを示すのに役立つ場合があります。上記のようにB.hのみを含める場合、コードの変更後にツール/人間が不要なインクルードを検出するのは困難です。
それは、その推移的な包含が必要なのか(たとえば、基本クラス)なのか、実装の詳細なのか(プライベートメンバー)なのかによって異なります。
明確にするために、中間ヘッダーで宣言されたインターフェースを最初に変更した後でのみ削除できる場合、推移的包含が必要です。これは既に重大な変更であるため、それを使用する.cppファイルはいずれにしてもチェックする必要があります。
例:A.hは、C.cppで使用されるB.hに含まれています。 B.hが実装の詳細にA.hを使用した場合、C.cppはB.hがこれを継続するとは想定しないでください。ただし、B.hが基本クラスにA.hを使用する場合、C.cppは、B.hがその基本クラスに関連するヘッダーを引き続き含むと想定する場合があります。
ヘッダーのインクルードを複製しないことの実際の利点がここに表示されます。 B.hが使用する基本クラスは実際にはA.hに属しておらず、B.h自体にリファクタリングされているとしましょう。 B.hはスタンドアロンヘッダーになりました。 C.cppにA.hが重複して含まれていた場合、不要なヘッダーが含まれるようになりました。
私は、提案された回答とは少し異なる同様のアプローチを取っています。
ヘッダーには、常に最低限、コンパイルパスを作成するために必要なものだけを含めます。可能な限り前方宣言を使用します。
ソースファイルでは、どれだけ含めるかはそれほど重要ではありません。私の好みはまだそれを通過させるために最小限を含めることです。
あちこちにヘッダーを含む小さなプロジェクトの場合、違いはありません。しかし、中規模から大規模のプロジェクトでは、問題になる可能性があります。コンパイルに最新のハードウェアが使用されている場合でも、違いが顕著になることがあります。その理由は、コンパイラーがインクルードされたヘッダーを開いて解析する必要があるためです。したがって、ビルドを最適化するには、上記の手法を適用します(最低限必要なものを含め、前方宣言を使用します)。
少し時代遅れですが、 Large Scale C++ Software Design (by John Lakos)が、これらすべてを詳細に説明しています。