50

大規模な C/C++ プロジェクトに取り組んでいるとき、ソース ファイルまたはヘッダー ファイル内の#includeに関する特定のルールはありますか?

たとえば、次の 2 つの過剰なルールのいずれかに従うことを想像できます。

  1. #includeは.hファイルでは禁止されています。必要なすべてのヘッダーを含めるのは、各.cファイル次第です。
  2. .hファイルには、すべての依存関係が含まれている必要があります。つまり、エラーなしで単独でコンパイルできる必要があります。

どのプロジェクトにもトレードオフがあると思いますが、あなたのプロジェクトは何ですか? もっと具体的なルールはありますか?または、解決策のいずれかを主張するリンクはありますか?

4

13 に答える 13

41

H ファイルのみを C ファイルに含めると、H ファイルを C ファイルに含めると、コンパイルが失敗する可能性があります。他の 20 個の H ファイルを前もって含める必要があり、さらに悪いことに、それらを正しい順序で含める必要があるため、失敗する可能性があります。非常に多くの H ファイルがあるため、このシステムは長期的には管理上の悪夢になります。あなたがしたかったことは、1 つの H ファイルをインクルードすることだけでした。他のどの H ファイルをどの順序でインクルードする必要があるかを調べるのに 2 時間も費やしました。

別の H ファイルが最初に含まれる場合にのみ H ファイルを C ファイルに正常に含めることができる場合は、最初の H ファイルに 2 番目の H ファイルを含める必要があります。そうすれば、すべての H ファイルを好きなすべての C ファイルに簡単にインクルードすることができ、コンパイルが中断されることを恐れることはありません。そうすれば、直接の依存関係のみを指定しますが、これらの依存関係自体にも依存関係がある場合は、それらを指定する必要があります。

一方、必要でない場合は、H ファイルを H ファイルに含めないでください。hashtable.hハッシュテーブルの実装を使用するために必要な他のヘッダー ファイルのみを含める必要があります。実装自体が を必要とする場合は、 ではなく にhashing.h含めます。これは、最終的なハッシュテーブルのみを使用したいコードではなく、実装のみが必要とするためです。hashtable.chashtable.h

于 2008-10-08T10:14:44.747 に答える
16

提案されたルールはどちらも悪いと思います。私の部分では、私は常に適用します:

このヘッダーで定義されているもののみを使用してファイルをコンパイルするために必要なヘッダー ファイルのみを含めます。これの意味は:

  1. 参照またはポインターとしてのみ存在するすべてのオブジェクトは、前方宣言する必要があります
  2. ヘッダー自体で使用される関数またはオブジェクトを定義するすべてのヘッダーを含めます。
于 2008-10-08T09:32:11.587 に答える
13

私はルール2を使用します:

すべてのヘッダーは、次の方法で自給自足できる必要があります。

  • 他の場所で定義されたものを使用しない
  • 他の場所で定義された前方宣言シンボル
  • 前方宣言できないシンボルを定義するヘッダーを含めます。

したがって、空の C/C++ ソース ファイルがある場合、ヘッダーをインクルードすると正しくコンパイルされます。

次に、C/C++ ソース ファイルに、必要なものだけを含めます。HeaderA が HeaderB で定義されたシンボルを前方宣言し、このシンボルを使用する場合は、両方を含める必要があります...前方宣言されたシンボルを使用しないでください。そうすると、HeaderA のみを含めて、HeaderB を含めないようにすることができます。

テンプレートをいじると、この検証は「ヘッダーを含む空のソースをコンパイルする必要があります」とやや複雑になることに注意してください(そして面白い...)

于 2008-10-08T10:22:57.007 に答える
8

最初のルールは、循環依存関係があるとすぐに失敗します。したがって、厳密に適用することはできません。

(これはまだ機能させることができますが、これはプログラマーからこれらのライブラリーの消費者に多くの作業を移すことになり、明らかに間違っています。)

私はすべてルール 2 に賛成です (ただし、実際の処理ではなく、「前方宣言ヘッダー」を含めることは良いかもしれません<iosfwd>。これにより、コンパイル時間が短縮されるからです)。一般に、ヘッダー ファイルがその依存関係を「宣言」している場合、それは一種の自己文書化であると考えています。これを行うには、必要なファイルをインクルードするよりも良い方法はありません。

編集:

コメントの中で、ヘッダー間の循環依存は悪い設計の兆候であり、避けるべきであることに挑戦しました。

それは正しくありません。実際、クラス間の循環依存は避けられない可能性があり、設計が悪いことを示す兆候ではありません。例は豊富にあります。オブザーバーとサブジェクトの間に循環参照があるオブザーバー パターンについてだけ触れておきます。

クラス間の循環を解決するには、C++ では宣言の順序が重要であるため、前方宣言を使用する必要があります。現在、全体のファイル数を減らし、コードを集中化するために、この前方宣言を循環的に処理することは完全に受け入れられます。確かに、前方宣言が 1 つしかないため、次のケースはこのシナリオには当てはまりません。ただし、これがはるかに多いライブラリに取り組んできました。

// observer.hpp

class Observer; // Forward declaration.

#ifndef MYLIB_OBSERVER_HPP
#define MYLIB_OBSERVER_HPP

#include "subject.hpp"

struct Observer {
    virtual ~Observer() = 0;
    virtual void Update(Subject* subject) = 0;
};

#endif

// subject.hpp
#include <list>

struct Subject; // Forward declaration.

#ifndef MYLIB_SUBJECT_HPP
#define MYLIB_SUBJECT_HPP

#include "observer.hpp"

struct Subject {
    virtual ~Subject() = 0;
    void Attach(Observer* observer);
    void Detach(Observer* observer);
    void Notify();

private:
    std::list<Observer*> m_Observers;
};

#endif
于 2008-10-08T09:22:15.663 に答える
4

最小バージョンの 2. .h ファイルには、コンパイルに特に必要なヘッダー ファイルのみが含まれ、実用的な限り前方宣言と pimpl を使用します。

于 2008-10-08T10:15:38.503 に答える
4
  1. 常に何らかのヘッダーガードを用意してください。
  2. using namespaceヘッダーにステートメントを挿入して、ユーザーのグローバル名前空間を汚染しないでください。
于 2008-10-08T11:36:37.530 に答える
1

2番目のオプションを使用することをお勧めします。突然別のヘッダー ファイルが必要になるヘッダー ファイルに何かを追加したいという状況に陥ることがよくあります。また、最初のオプションでは、多くの C ファイルを調べて更新する必要があり、場合によっては自分で制御できないこともあります。2 番目のオプションでは、ヘッダー ファイルを更新するだけでよく、追加したばかりの新しい機能を必要としないユーザーは、あなたがそれを行ったことを知る必要さえありません。

于 2008-10-08T09:43:15.247 に答える
0

簡単に言えば、私はMeckiに同意します。

プロジェクト内のすべての foo.h に対して、作成に必要なヘッダーのみを含めます。

// foo.c
#include "any header"
// end of foo.c

コンパイル。

(プリコンパイル済みヘッダーを使用する場合は、もちろん許可されます。たとえば、MSVC の #include "stdafx.h" など)。

于 2008-10-26T00:59:52.373 に答える
0

個人的には、次のようにし
ます。 1 .h ファイルに他の .h ファイルを含めるように前方宣言します。その .h ファイルまたはクラスでポインタ/参照として何かを使用できる場合、コンパイル エラーなしで前方宣言が可能です。これにより、ヘッダーに含まれる依存関係が少なくなる可能性があります (コンパイル時間を節約できますか? わかりません:( ).
2 .h ファイルを単純または具体的にします。たとえば、CONST.h というファイルですべての定数を定義するのは良くありません。 CONST_NETWORK.h、CONST_DB.h のような複数のもの. したがって、DB の 1 つのインスタンスを使用するために、ネットワークに関する他の情報を含める必要はありません.
3 ヘッダーに実装を入れないでください. ; それらを実装するときは、宣言を他の詳細で汚染しないでください。

于 2013-12-14T04:03:42.710 に答える
0

これは、インターフェイスの設計に帰着します。

  1. 常に参照またはポインターで渡します。ポインターをチェックしない場合は、参照渡ししてください。
  2. 可能な限り前方宣言します。
  3. クラスで new を使用しないでください。ファクトリを作成してそれを行い、それらをクラスに渡します。
  4. プリコンパイル済みヘッダーは使用しないでください。

Windows では、私の stdafx には afx___.h ヘッダーのみが含まれます。文字列、ベクター、またはブースト ライブラリは含まれません。

于 2008-10-08T10:39:35.773 に答える
0

ルール番号 1 では、ヘッダー ファイルを非常に特定の順序でリストする必要があり (基本クラスのインクルード ファイルは、派生クラスのインクルード ファイルの前に配置する必要があるなど)、順序を間違えるとコンパイル エラーが発生しやすくなります。

トリックは、他のいくつかが言及したように、参照またはポインターが使用されている場合、可能な限り前方宣言を使用することです。この方法でビルドの依存関係を最小限に抑えるには、pimplイディオムが役立ちます。

于 2008-10-09T12:24:02.753 に答える
0

ポイント。1 特定のヘッダーを介してヘッダーをプリコンパイルしたい場合は失敗します。例えば。これは、VisualStudio での StdAfx.h の目的です。すべての一般的なヘッダーをそこに配置します...

于 2008-10-08T10:07:16.710 に答える