84

何らかの理由で、当社には次のようなコーディングガイドラインがあります。

Each class shall have it's own header and implementation file.

したがって、というクラスMyStringを作成した場合、関連付けられたMyStringh.hMyString.cxxが必要になります。

他の誰かがこれをしますか?結果として、コンパイルパフォーマンスへの影響を見た人はいますか?10000ファイルの5000クラスは、2500ファイルの5000クラスと同じくらい速くコンパイルされますか?そうでない場合、違いは顕著ですか?

[私たちはC++をコーディングし、日常のコンパイラとしてGCC3.4.4を使用しています]

4

13 に答える 13

86

ここでの用語は翻訳単位であり、(可能であれば)翻訳単位ごとに1つのクラス、つまり、同じ名前の対応する.hファイルを持つ.cppファイルごとに1つのクラス実装が必要です。

特にインクリメンタルリンクなどを実行している場合は、通常、この方法で実行する方が(コンパイル/リンクの観点から)より効率的です。つまり、翻訳ユニットは分離されているため、1つの翻訳ユニットが変更されたときに、多くの抽象化を1つの翻訳ユニットにまとめ始めた場合のように、多くのものを再構築する必要はありません。

また、多くのエラー/診断がファイル名( "Myclass.cppのエラー、22行目")で報告されており、ファイルとクラスが1対1で対応している場合に役立ちます。(または、2対1の通信と呼ぶことができると思います)。

于 2008-08-26T14:34:39.797 に答える
75

数千行のコードに圧倒されましたか?

ディレクトリ内のクラスごとに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が含まれます。

ただし、テンプレートがあるため、実際には上記のパターンを再現する必要がありますが、ヘッダーのみを使用します。

これの意味は:

  1. 定義ヘッダーA.def.hppおよびB.def.hpp
  2. 実装ヘッダーA.inl.hppおよびB.inl.hpp
  3. 便宜上、「ナイーブ」ヘッダーA.hppおよびB.hpp

各ヘッダーには、次の特性があります。

  1. A.def.hpp(またはB.def.hpp)には、クラスB(またはA)の前方宣言があります。これにより、そのクラスへのポインター/参照を宣言できます。
  2. A.inl.hpp(またはB.inl.hpp)には、A.def.hppとB.def.hppの両方が含まれます。これにより、A(またはB)のメソッドがクラスB(またはA)を使用できるようになります。 。
  3. A.hpp(またはB.hpp)には、A.def.hppとA.inl.hpp(またはB.def.hppとB.inl.hpp)の両方が直接含まれます。
  4. もちろん、すべてのヘッダーは自給自足であり、ヘッダーガードによって保護されている必要があります

ナイーブなユーザーにはA.hppやB.hppが含まれるため、混乱全体が無視されます。

そして、その編成を持つということは、ライブラリ作成者がAとBの間の循環依存を解決しながら、両方のクラスを別々のファイルに保持できることを意味します。スキームを理解すれば、簡単にナビゲートできます。

これはエッジケースであることに注意してください(2つのテンプレートがお互いを知っています)。私はほとんどのコードがそのトリックを必要としないことを期待しています。

于 2008-09-19T20:30:44.897 に答える
12

私たちは仕事でそれを行います。クラスとファイルが同じ名前である場合、ものを見つけるのは簡単です。パフォーマンスに関しては、1つのプロジェクトに5000のクラスを含めるべきではありません。そうした場合、いくつかのリファクタリングが適切である可能性があります。

そうは言っても、1つのファイルに複数のクラスがある場合があります。そして、それがファイルのメインクラスのプライベートヘルパークラスである場合です。

于 2008-08-26T14:23:12.040 に答える
10

分離のための+1。一部のクラスが別の名前のファイルにあるか、別のクラスにまとめられているプロジェクトにたどり着きましたが、これらをすばやく効率的に見つけることは不可能です。ビルドでより多くのリソースを投入できます。編集する適切なファイルが見つからないため、失われたプログラマーの時間を埋め合わせることができません。

于 2008-08-26T14:28:28.050 に答える
9

単に「明確」であることに加えて、クラスを別々のファイルに分割することで、複数の開発者がお互いに足を踏み入れないようにすることが容易になります。バージョン管理ツールに変更をコミットするときは、マージが少なくなります。

于 2008-08-26T14:34:40.467 に答える
7

私が働いたほとんどの場所は、この慣習に従っています。私は実際にBAE(オーストラリア)のコーディング標準を作成しました。その理由は、正当な理由がなく、石に何かを彫るだけではない理由です。

ソースファイルに関する質問に関しては、コンパイルする時間はそれほど多くありませんが、最初に関連するコードスニペットを見つけることができるという問題があります。誰もがIDEを使用しているわけではありません。また、MyClass.hとMyClass.cppを探すだけで、大量のファイルに対して「grep MyClass *。(h | cpp)」を実行し、#includeMyClass.hステートメントを除外するよりも時間を節約できます。

コンパイル時間に多数のソースファイルが影響する場合の回避策があることに注意してください。興味深い議論については、JohnLakosによる大規模C++ソフトウェア設計を参照してください。

コーディングガイドラインに関する優れた章については、SteveMcConnellによるCodeCompleteを読むこともできます。実は、この本は私が定期的に戻ってくる素晴らしい本です。

于 2008-08-26T14:31:52.940 に答える
3

これを行うのが一般的な方法です。特に、それを必要とするファイルに.hを含めることができるようにするためです。もちろん、パフォーマンスは影響を受けますが、発生するまでこの問題について考えないようにしてください:)。
分離したファイルから始めて、その後、本当に必要な場合は、パフォーマンスを向上させるために一般的に一緒に使用される.hをマージしてみることをお勧めします。それはすべてファイル間の依存関係に帰着し、これは各プロジェクトに非常に固有です。

于 2008-08-26T14:25:31.287 に答える
3

他の人が言ったように、ベスト プラクティスは、コードのメンテナンスと理解しやすさの観点から、各クラスを独自の翻訳単位に配置することです。ただし、大規模なシステムでは、これはお勧めできない場合があります。トレードオフの説明については、Bruce Dawson によるこの記事の「ソース ファイルを大きくする」というタイトルのセクションを参照してください。

于 2008-08-27T16:41:01.733 に答える
2

ヘッダー ファイルに関しては、これらのガイドラインが特に役立つことがわかりました。 http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Header_Files

于 2010-12-01T22:29:34.330 に答える
1

ほとんどの人がクラスごとに 1 つのファイルを持つことに賛成していることに驚いています。それに関する問題は、「リファクタリング」の時代に、ファイルとクラス名を同期させるのに苦労するかもしれないということです。クラス名を変更するたびに、ファイル名も変更する必要があります。つまり、ファイルが含まれるすべての場所で変更を行う必要があります。

私は個人的に関連するクラスを 1 つのファイルにグループ化し、そのようなファイルに、クラス名が変わっても変更する必要のない意味のある名前を付けます。ファイル数が少ないと、ファイル ツリーのスクロールも簡単になります。私は Windows では Visual Studio を、Linux では Eclipse CDT を使用していますが、どちらにもクラス宣言に直接アクセスできるショートカット キーがあるため、クラス宣言を簡単かつ迅速に見つけることができます。

そうは言っても、プロジェクトが完了するか、その構造が「固まり」、名前の変更がまれになると、ファイルごとに1つのクラスを持つことが理にかなっていると思います。クラスを抽出して、それらを個別の .h ファイルと .cpp ファイルに配置できるツールがあればいいのにと思います。しかし、私はこれを本質的なものとは考えていません。

選択は、作業するプロジェクトのタイプによっても異なります。私の意見では、どちらの選択にも長所と短所があるため、この問題は白黒の答えに値しません.

于 2015-01-07T14:33:45.183 に答える
1

ファイルごとにクラスを 1 つだけ持つことは非常に便利ですが、個々の C++ ファイルをすべて含むバルクビルド ファイルを使用してビルドを行うと、多くのコンパイラで起動時間が比較的長いため、コンパイルが高速になります。

于 2008-09-18T05:12:13.187 に答える
0

ここでも同じルールが適用されますが、許可されている場合はいくつかの例外があります。

  • 継承ツリー
  • 非常に限られた範囲内でのみ使用されるクラス
  • 一部のユーティリティは、単に一般的な「utils.h」に配置されます
于 2008-08-26T14:30:11.557 に答える