26


8

CプロジェクトまたはCプロジェクトで#includeステートメントをどのように保守しますか? ファイル内のインクルード文のセットが最終的に不十分である(しかし、プロジェクトの現在の状態のために機能するようになる)か、必要でなくなったものをインクルードすることは、ほとんど避けられないようです。

問題を発見または修正するためのツールを作成しましたか? 助言がありますか?

#includeステートメントを削除するたびに、ヘッダー以外の各ファイルを個別にコンパイルするようなものを何度も書くことを考えていました。 最小限のインクルードが得られるまでこれを続けます。

ヘッダーファイルに必要なものがすべて揃っていることを確認するために、ヘッダーファイルが含まれているだけのソースファイルを作成し、それをコンパイルします。 コンパイルが失敗した場合は、ヘッダファイル自体にインクルードファイルがありません。

私が何かを作成する前に、私はここで尋ねるべきだと思いました。 これはやや普遍的な問題のようです。

12 Answer


23


_ ヘッダーファイルに必要なものがすべて含まれていることを確認するには、ヘッダーファイルを含めるだけでソースファイルを作成し、コンパイルを試みます。 コンパイルが失敗した場合は、ヘッダファイル自体にインクルードファイルがありません。 _

次の規則を作ることによって同じ効果を得ることができます:foo.cまたは_foo_.cppが含まなければならない最初のヘッダーファイルは対応する名前の_foo_.hであるべきです。 こうすることで、foo.hにはコンパイルに必要なものがすべて含まれるようになります。

さらに、Lakosの著書「大規模Cソフトウェア設計」(例えば)には、実装の詳細をヘッダーから対応するCPPファイルに移動するための多数の、多くの技法がリストされています。 Cheshire Cat(すべての実装の詳細を隠す)やFactory(サブクラスの存在を隠す)のようなテクニックを使ってそれを極端にすると、他のヘッダーを含めずに多くのヘッダーを単独で使用できます。代わりに不透明(OPAQUE)型への宣言をしています…​ おそらくテンプレートクラスを除いて。

最後に、各ヘッダーファイルに次のものを含める必要があります。

  • データメンバーである型のヘッダーファイルはありません(代わりに、データ メンバーは、「cheshire cat」a.k.aを使用してCPPファイルで定義/非表示されます。 "pimpl"テクニック)

  • 型のパラメーターであるか、型から返される型のヘッダーファイルはありません メソッド(代わりに、これらは `+ int `のような定義済みの型です;または、それらがユーザー定義型の場合、それらは参照です。その場合、単に ` class Foo; `のような前方宣言された不透明な型宣言です。ヘッダーファイルの `#include" foo.h "+`の代わりに十分です)。

その時あなたが必要とするものは以下のためのヘッダファイルです:

  • スーパークラス(これがサブクラスの場合)

  • おそらくメソッドのパラメーターとして使用されるテンプレート化された型 および/または戻り値の型:どうやらテンプレートクラスも前方宣言できるはずですが、コンパイラの実装によっては問題がある場合があります(テンプレートをカプセル化することもできますが、 ユーザー定義型の実装の詳細としての「+ List 」 ` ListX +`)。

実際には、私はすべてのシステムファイルを含む "standard.h"を作るかもしれません(例: プロジェクトのヘッダーファイルで使用されるSTLヘッダー、O / S固有のタイプ、および +#define + sなど)、すべてのアプリケーションヘッダーファイルの最初のヘッダーとして(およびこの「standard.h」を「プリコンパイル済みヘッダーファイル」として扱うコンパイラ。

'' '' '

//contents of foo.h
#ifndef INC_FOO_H //or #pragma once
#define INC_FOO_H

#include "standard.h"
class Foo
{
public: //methods
  ... Foo-specific methods here ...
private: //data
  struct Impl;
  Impl* m_impl;
};
#endif//INC_FOO_H

'' '' '

//contents of foo.cpp
#include "foo.h"
#include "bar.h"
Foo::Foo()
{
  m_impl = new Impl();
}
struct Foo::Impl
{
  Bar m_bar;
  ... etc ...
};
... etc ...


10


私は自分のインクルードを高い抽象度から低い抽象度へと注文する習慣があります。 これにはヘッダが自給自足でなければならず、隠された依存関係がコンパイラエラーとしてすぐに明らかにされることが必要です。

例えば、クラス 'Tetris’はTetris.hとTetris.cppファイルを持っています。 Tetris.cppのインクルード順序は次のようになります。

#include "Tetris.h" //最初に対応するヘッダー#include "Block.h" // ..アプリケーションレベルを含む#include "Utils / Grid.h" // ..ライブラリの依存関係#include // ..then stl #include // ..システムインクルード

そして今、私は、このシステムは不要なインクルードをクリーンアップするのには本当に役立っていないので、これはあなたの質問には実際に答えないことを理解しています。 まぁ..


6


余分なインクルードの検出については、すでにhttps://stackoverflow.com/questions/614794/c-c-detecting-superfluous-includes [この質問]で説明しています。

私は不十分だが偶然の仕事が含まれていることを検出するのに役立つどんなツールも知らないが、良いコーディング規約はここで助けることができる。 たとえば、http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Names_and_Order_of_Includes[Google C Style Guide]では、隠れた依存関係を減らすことを目的として、次のことが義務付けられています。

`+ dir2 / foo2.h `の内容を実装またはテストすることが主な目的である ` dir / foo.cc +`では、インクルードを次のように順序付けます。

  1. + dir2 / foo2.h +(望ましい場所—以下の詳細を参照)。

  2. Cシステムファイル

  3. Cシステムファイル

  4. 他のライブラリの.hファイル

  5. あなたのプロジェクトの.hファイル。


6


プロジェクトのサイズに応じて、http://www.doxygen.nl/ [doxygen]( `+ INCLUDE_GRAPH +`オプションをオンにして)によって作成されたインクルードグラフを確認すると役立ちます。


5


ヘッダの削除と再コンパイルのテクニックに伴う1つの大きな問題は、それがまだコンパイルされているが間違っているか非効率的なコードにつながる可能性があるということです。

  1. テンプレートの特殊化:あるヘッダに含まれる特定のタイプと別のヘッダに含まれるより一般的なテンプレートのテンプレート特殊化がある場合、特殊化を削除するとコードがコンパイル可能な状態のままになることがありますが、望ましくありません。

  2. オーバーロードの解決:同様の問題 - 異なるヘッダに1つの関数のオーバーロードが2つあっても、互換性のある型になっている場合は、1つのケースに適したバージョンを削除してもコードはコンパイルできます。 これはおそらくテンプレート特化版よりも可能性が低いですが、可能です。


2


_ #includeステートメントを削除するたびに、ヘッダー以外の各ファイルを個別にコンパイルするようなものを何度も書くことを考えていました。 最小限のインクルードが得られるまでこれを続けます。 _

これは誤解されていると思いますし、「不十分だがうまくいくだけの」インクルードセットにつながるでしょう。

ソースファイルが `+ numeric_limits `を使用しているが、ヘッダーファイルも含まれているとします。ヘッダーファイルには、独自の理由で ``が含まれています。 これは、ソースファイルに ``を含めるべきではないという意味ではありません。 他のヘッダーファイルは、おそらく `+`で定義されたすべてを定義するように文書化されていません。 いつか停止する可能性があります。ある関数のデフォルトパラメータとして1つの値のみを使用する場合や、そのデフォルト値が `+ std

numeric_limits :: min()+`から0に変更される場合があります。 そして今、あなたのソースファイルはもうコンパイルされません、そしてそのヘッダファイルのメンテナはあなたのファイルが彼のビルドを壊すまで存在したことさえ知りませんでした。

現時点で問題のあるビルドの問題が発生していない限り、冗長なインクルードを削除する最善の方法は、メンテナンスのためにファイルにアクセスするたびにリストを参照する習慣を身に付けることです。 もしあなたが何十ものインクルードを持っていて、そしてそのファイルをレビューしたとしても、それぞれが何のためにあるのか理解することができないなら、もっと小さいファイルに分割することを検討してください。


2


Visual Studioコンパイラを使用している場合は、/ showIncludesコンパイラオプションを試してから、stderrに出力される内容を解析できます。 MSDN: "コンパイラにインクルードファイルのリストを出力させます。 入れ子になったインクルードファイル(インクルードしたファイルからインクルードされたファイル)も表示されます。 "


1


うん。 私たちには私たち自身のマクロ言語へのアクセスを与える私たち自身のプリプロセッサがあります。 ヘッダーファイルが一度だけ含まれることも確認します。 単純なプリプロセッサを作成して複数のインクルードをチェックするのはかなり簡単なはずです。


1


ツールに関しては、Imagix(これは約6年前のことです)を使用して、不要なインクルードと、必要なインクルードを別のインクルードを通して間接的にインクルードしたものを識別しました。


1


cppcleanプロジェクトをご覧ください。 彼らはまだその機能を実装していませんが、それは行われる予定です。

プロジェクトサイトから:

_ _ CppCleanは、特に大規模なコードベースでの開発を遅らせるC ++ソースの問題を見つけようとします。 それはlintに似ています。しかしながら、CppCleanは他の静的解析ツールと同様に局所的な問題よりもむしろグローバルなモジュール間の問題を見つけることに焦点を合わせています。

目標は、未使用のコードを残して時間の経過とともに変更される大規模コードベースでの開発を遅らせる問題を見つけることです。 このコードは、未使用の関数、メソッド、データメンバ、型など、不要な#includeディレクティブまで、さまざまな形式で提供されます。 不必要な#includeはかなりの余分なコンパイルを引き起こす可能性があり、編集 - コンパイル - 実行サイクルを増加させます。 _ _

そして特に#include機能に関して:

_ * (予定)不要なヘッダーファイルを検索#included * ヘッダーには何も直接参照しません * 代わりにクラスが前方宣言されている場合、ヘッダーは不要です * (予定)直接#includedされないヘッダーを参照するソースファイル、 すなわち、別のヘッダーからの推移的な#includeに依存するファイル _

https://bitbucket.org/robertmassaioli/cppclean [こちら] BitBucketにミラーがあります。