一時的に含まれるヘッダーに依存することは良い習慣ですか?


37

私が取り組んでいるC ++プロジェクトのインクルードをクリーンアップしていますが、特定のファイルで直接使用されるすべてのヘッダーを明示的にインクルードする必要があるのか​​、最低限だけインクルードする必要があるのか​​疑問に思っています。

以下に例を示しEntity.hppます。

#include "RenderObject.hpp"
#include "Texture.hpp"

struct Entity {
    Texture texture;
    RenderObject render();
}

(の前方宣言RenderObjectはオプションではないと仮定しましょう。)

今、私はそれがRenderObject.hpp含まれていることを知っていますTexture.hpp-私はそれぞれRenderObjectTextureメンバーを持っているのでそれを知っています。それでも、私は明示的に含めるTexture.hppにはEntity.hpp、それはそれはに含まれているに依存することをお勧めします場合、私はわからないので、RenderObject.hpp

だから:それは良い習慣ですか?


19
あなたの例のインクルードガードはどこにありますか?偶然それらを忘れてしまったのでしょうか?
Doc Brown 14年

3
すべての使用済みファイルを含めない場合に発生する問題の1つは、ファイルを含める順序が重要になる場合があることです。それはそれが起こるただ一つのケースでは本当に迷惑ですが、時には雪だるま式になり、そのようなコードを書いた人が射撃隊の前で行進することを本当に望みます。
ダンク

これがある理由です#ifndef _RENDER_H #define _RENDER_H ... #endif
sampathsris

@ダンク問題を誤解したと思います。起こるべきではない彼の提案のいずれかで。
Mooingダック14年

1
@DocBrown、#pragma once解決しましたか?
Pacerier

回答:


65

.cppファイルで使用されるオブジェクトを定義するすべてのヘッダーを、それらのファイルの内容に関する知識に関係なく、そのファイルに常に含める必要があります。ヘッダーを複数回インクルードすることが問題にならないように、すべてのヘッダーファイルにガードを含める必要があります。

その理由:

  • これにより、問題のソースファイルに必要なソースを正確に読み取る開発者にとって明確になります。ここでは、ファイルの最初の数行を見るとTexture、このファイル内のオブジェクトを扱っていることがわかります。
  • これにより、特定のヘッダー自体が不要になったときに、リファクタリングされたヘッダーがコンパイルの問題を引き起こす問題を回避できます。たとえば、それRenderObject.hppが実際にTexture.hppそれ自体を必要としないことに気づいたとします。

当然のことながら、そのファイルで明示的に必要な場合を除き、ヘッダーを別のヘッダーに含めないでください。


10
結果に同意します-ただし、但し書きが必要な場合は、常に他のヘッダーを含める必要があります!
アンドリュー14年

1
私はすべての個々のクラスにヘッダーを直接含めることを嫌います。累積ヘッダーに賛成です。つまり、高レベルのファイルは、使用している何らかの種類の「モジュール」を参照する必要がありますが、個々のパーツすべてを直接含める必要はないと思います。
edA-qa mort-ora-y 14年

8
そのため、すべてのファイルに含まれる大きなモノリシックヘッダーが生成されます。ヘッダーに含まれる内容が少しでも必要な場合でも、コンパイル時間が長くなり、リファクタリングが困難になります。
ロボットを

6
Googleはinclude-what-you-youと呼ばれるこのアドバイスを正確に実施するのに役立つツールを構築しました。
マシューG。14年

3
大きなモノリシックヘッダーの主なコンパイル時の問題は、ヘッダーコード自体をコンパイルするときではなく、ヘッダーが変更されるたびにアプリケーションのすべてのcppファイルをコンパイルする必要があることです。プリコンパイル済みヘッダーはそれを助けません。
ロボットを

23

一般的な経験則は、使用するものを含めることです。オブジェクトを直接使用する場合は、ヘッダーファイルを直接インクルードします。Bを使用し、自分でBを使用しないオブジェクトAを使用する場合は、Ahのみを含めます

また、このトピックについては、ヘッダーで実際に必要な場合にのみ、ヘッダーファイルに他のヘッダーファイルを含める必要があります。.cppでのみ必要な場合は、そこにのみ含めてください。これは、パブリック依存関係とプライベート依存関係の違いであり、クラスのユーザーが実際に必要のないヘッダーにドラッグできないようにします。


10

特定のファイルで直接使用されるすべてのヘッダーを明示的に含める必要があるかどうか疑問に思う

はい。

他のヘッダーがいつ変更されるかはわかりません。各翻訳単位に、翻訳単位が必要とすることがわかっているヘッダーを含めることは、世界ですべての意味をなします。

二重包含が有害でないことを保証するヘッダーガードがあります。


3

これに関する意見は異なりますが、私はすべてのファイル(c / cppソースファイルまたはh / hppヘッダーファイル)を単独でコンパイルまたは分析できる必要があると考えています。

そのため、すべてのファイルには、必要なすべてのヘッダーファイルを#includeする必要があります。1つのヘッダーファイルが既に含まれていると想定しないでください。

ヘッダーファイルを追加して、他の場所で定義されたアイテムを使用していることを見つける必要がある場合、それを直接含める必要はありません...

反対に、不要なファイルを#includeする場合、(一般的なルールとして)問題ではありません...


個人的なスタイルのポイントとして、#includeファイルをアルファベット順に整理し、システムとアプリケーションに分割します。これにより、「自己完結型で完全に一貫した」メッセージを強化できます。


インクルードの順序に関する注意:X11ヘッダーを含める場合など、順序が重要になる場合があります。これは設計が原因である可能性があり(その場合、設計が不適切と見なされる可能性があります)、不幸な非互換性の問題が原因である場合があります。
ハイド14年

不要なヘッダーを含めることに関する注意事項は、最初に直接(特にテンプレートが重いC ++の場合)コンパイル時間に関係しますが、特に、インクルードファイルが変更される同じプロジェクトまたは依存関係プロジェクトのヘッダーを含める場合は、再コンパイルがトリガーされますそれを含むすべて(動作する依存関係がある場合、そうでない場合は、常にクリーンビルドを実行する必要があります...)。
ハイド14年

2

それは、その推移的な包含が必然的なものか(例:基本クラス)か、実装の詳細のためか(プライベートメンバー)に依存します。

明確にするために、中間ヘッダーで宣言されたインターフェイスを最初に変更した後にのみ削除を行うことができる場合、推移的な包含が必要です。これはすでに重大な変更であるため、それを使用する.cppファイルはチェックする必要があります。

例:AhはC.cppで使用されるBhに含まれています。Bhが実装の詳細のためにAhを使用した場合、C.cppはBhが引き続き使用すると想定しないでください。しかし、Bhが基本クラスにAhを使用する場合、C.cppは、Bhがその基本クラスに関連するヘッダーを引き続き含むと想定する場合があります。

ここに、ヘッダーの包含を複製しないことの実際の利点があります。Bhが使用する基本クラスは実際にはAhに属しておらず、Bh自体にリファクタリングされているとします。Bhは現在、スタンドアロンのヘッダーです。C.cppに冗長にAhが含まれていた場合、不要なヘッダーが含まれるようになりました。


2

別のケースがあります:Ah、Bh、およびC.cppがあり、BhにはAhが含​​まれます

C.cppでは、次のように書くことができます

#include "B.h"
#include "A.h" // < this can be optional as B.h already has all the stuff in A.h

ここで#include "Ah"を記述しないと、どうなりますか?C.cppでは、AとBの両方(クラスなど)が使用されます。後でC.cppコードを変更し、B関連のものを削除しましたが、Bhはそこに含まれたままにします。

AhとBhの両方を含め、この時点で不要なインクルードを検出するツールを使用すると、Bhインクルードが不要になったことを示すのに役立ちます。上記のようにBhのみを含めると、コードの変更後にツール/人間が不要なインクルードを検出するのは困難です。


1

私は、提案された回答と同様のわずかに異なるアプローチを取っています。

ヘッダーには、コンパイルのパスを作成するために必要な最低限のものだけを常に含めてください。可能な限り前方宣言を使用してください。

ソースファイルでは、どれだけ含めるかはそれほど重要ではありません。私の好みは、それを通過させるために最低限を含めることです。

こことそこにヘッダーを含む小さなプロジェクトの場合、違いはありません。しかし、中規模から大規模のプロジェクトでは、問題になる可能性があります。最新のハードウェアを使用してコンパイルした場合でも、違いは顕著です。理由は、コンパイラーがインクルードされたヘッダーを開いて解析する必要があるためです。したがって、ビルドを最適化するには、上記の手法を適用します(最低限必要なものを含め、前方宣言を使用します)。

少し時代遅れですが、Large Scale C ++ Software Design(by John Lakos)がこのすべてを詳細に説明しています。


1
この戦略に同意しない...ヘッダーファイルをソースファイルに含める場合、その依存関係をすべて追跡する必要があります。リストを文書化して試すよりも、直接含める方が良いです!
アンドリュー14年

@Andrewには、何が何回含まれているかをチェックするツールとスクリプトがあります。
BЈовић

1
これに対処するために、最新のコンパイラのいくつかで最適化が行われていることに気付きました。彼らは典型的なガードステートメントを認識し、処理します。その後、再び#includeするとき、ファイルのロードを完全に最適化できます。ただし、前方宣言の推奨は、インクルードの数を減らすために非常に賢明です。前方宣言の使用を開始すると、コンパイラー実行時(前方宣言によって改善される)と使いやすさ(便利な追加#includesによって改善される)のバランスになります。これは、各企業が別々に設定するバランスです。
コートアンモン14年

1
@CortAmmon A典型的なヘッダは、警備員を含む持っていますが、コンパイラは、まだそれを開く必要があり、それは低速動作である
BЈовић

4
@BЈовић:実際にはそうではありません。彼らがしなければならないことは、ファイルに「典型的な」ヘッダーガードがあることを認識し、一度だけ開くようにフラグを立てることです。たとえば、Gccには、この最適化をいつどこで適用するかに関するドキュメントがあります。gcc.gnu.org
Cort Ammon

-4

ヘッダー戦略がコンパイルされている限り、ヘッダー戦略を心配しないことをお勧めします。

コードのヘッダーセクションは、簡単に解決できるコンパイルエラーが発生するまで誰も見てはいけない行のブロックです。「正しい」スタイルへの欲求は理解していますが、どちらの方法も正しいとは言えません。すべてのクラスにヘッダーを含めると、厄介な順序ベースのコンパイルエラーが発生する可能性が高くなりますが、これらのコンパイルエラーは、注意深いコーディングで修正できる問題も反映します(ただし、修正する時間はないはずです)。

そして、はい、あなたfriend着陸を開始する、それらの順序に基づいた問題が発生します。

この問題は2つの場合に考えられます。


ケース1:相互に対話するクラスの数が少ない場合(たとえば、12個未満)。 これらのヘッダーは、相互の依存関係に影響を与える可能性のある方法で、定期的に追加、削除、または変更します。これは、コード例が示唆するケースです。

ヘッダーのセットは十分に小さいため、発生する問題を解決するのは複雑ではありません。難しい問題は、1つまたは2つのヘッダーを書き換えることで修正されます。ヘッダー戦略を心配することは、存在しない問題を解決することです。


ケース2:多数のクラスがあります。 クラスの中にはプログラムのバックボーンを表すものがあり、それらのヘッダーを書き換えると、大量のコードベースの書き換え/再コンパイルが必要になります。他のクラスはこのバックボーンを使用して物事を達成します。これは典型的なビジネス設定を表します。ヘッダーはディレクトリ全体に分散しているため、すべての名前を現実的に思い出すことはできません。

解決策:この時点で、論理グループ内のクラスを考え、それらのグループをヘッダーに収集して、何度も繰り返す必要がなくなるようにする必要があります#include。これにより、作業が簡単になるだけでなく、プリコンパイル済みヘッダーを利用するための必要な手順にもなります。

あなたは終わる#includeクラスあなたが必要としませんが、INGの心配事を

この場合、コードは次のようになります...

#include <Graphics.hpp>

struct Entity {
    Texture texture;
    RenderObject render();
}

13
「良い習慣は、それがコンパイルされる限り____戦略を心配しないこと」という形の文は人々を悪い判断に導くと信じているので、これを-1にしなければなりませんでした。私は、アプローチが非常に急速に読みにくくなることを発見しました、そして、読みにくいことは「機能しない」と同じくらい悪いです。また、あなたが説明する両方のケースの結果に同意しない多くの主要なライブラリを見つけました。例として、Boost DOESは、ケース2で推奨する「コレクション」ヘッダーを実行しますが、必要に応じてクラスごとのヘッダーを提供することも大いに行います。
コートアンモン14年

3
個人的に「コンパイルしても心配しないでください」が「列挙型に値を追加すると、アプリケーションのコンパイルに30分かかります。どうすれば修正できますか?」に変わるのを目撃しました。
ロボット

私は答えでコンパイル時間の問題に対処しました。実際、私の答えはたった2つのうちの1つ(どちらも得点が高くない)です。しかし、実際には、それはOPの質問の正接です。これは「変数名をキャメルケースに入れるべきですか?」タイプの質問。私の答えは人気がありませんが、すべてのベストプラクティスが常にあるとは限りません。これはそのようなケースの1つです。
質問

#2に同意します。以前のアイデアについては-ローカルヘッダーブロックを更新する自動化を望んでいます-それまでは、完全なリストを推奨します。
chux-モニカの復元15年

「すべてとキッチンシンクを含める」アプローチでは、最初は少し時間を節約できます-ヘッダーファイルはさらに小さく見えるかもしれません(ほとんどのものはどこかから間接的に含まれているからです...)。どこかで変更を行うと、プロジェクトが30分以上再調整されるまでになります。また、IDEスマートオートコンプリートは、何百もの無関係な提案を表示します。そして、同じような名前の2つのクラスまたは静的関数を誤って2つ混同しています。そして、新しい構造体を追加しますが、どこかにまったく関係のないクラスと名前空間の衝突があるため、ビルドは失敗します...
CharonX
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.