web-dev-qa-db-ja.com

.cppファイルのみを含めるとすべてが機能するのに、.hを含める必要があるのはなぜですか?

.hファイルのみを含めることで機能させることができるのに、.cppファイルと.cppファイルの両方を含める必要があるのはなぜですか?

たとえば、file.hを含む宣言を作成し、次にfile.cppを含む定義を作成し、両方をmain.cppに含めます。

または、file.cppに宣言/定義(プロトタイプなし)を含むmain.cppを作成します。

どちらもうまくいきます。違いがわかりません。コンパイルとリンクのプロセスに関する洞察が役立つ場合があります。

19
reaffer

あなたが述べたようにあなたがすることができます.cppファイルを含めることができますが、これは悪い考えです。

あなたが述べたように、宣言はヘッダーファイルに属しています。これらは実装を含まないため、複数のコンパイルユニットに含まれていても問題はありません。関数またはクラスメンバーの定義を複数回インクルードすると、リンカが混乱してエラーをスローするため、通常は(常にではありませんが)問題が発生します。

.cppファイルには、クラス、論理的に編成された関数のグループ、グローバル静的変数(使用する場合は控えめに使用)など、プログラムのサブセットの定義が含まれます。

コンパイルユニット.cppファイル)には、含まれている定義をコンパイルするために必要な宣言が含まれています。参照しているが含まれていない関数とクラスを追跡しているため、後でオブジェクトコードを実行可能ファイルまたはライブラリに結合するときに、リンカーがそれらを解決できます。

  • Foo.h->クラスFooの宣言(インターフェース)が含まれています。
  • Foo.cpp-> Fooクラスの定義(実装)が含まれています。
  • Main.cpp-> mainメソッド、プログラムエントリポイントが含まれています。このコードはFooをインスタンス化して使用します。

Foo.cppMain.cppの両方にFoo.hを含める必要があります。 Foo.cppは、クラスインターフェイスをサポートするコードを定義しているため、そのインターフェイスが何であるかを知る必要があるため、このインターフェイスが必要です。 Main.cppは、Fooを作成してその動作を呼び出しているため、この動作が必要です。その動作とは何か、メモリ内のFooのサイズ、およびその機能を見つける方法などを知っている必要がありますが、まだ実際の実装。

コンパイラーは、Foo.oからFoo.cppを生成します。これには、すべてのFooクラスコードがコンパイルされた形式で含まれています。また、mainメソッドとクラスFooへの未解決の参照を含むMain.oを生成します。

2つのオブジェクトファイルFoo.oおよびMain.oを実行可能ファイルに結合するリンカーが登場しました。 Main.oの未解決のFoo参照を確認しますが、Foo.oには必要な記号が含まれているため、いわば「ドットを接続」しています。 Main.oの関数呼び出しは、コンパイルされたコードの実際の場所に接続されるようになり、実行時にプログラムが正しい場所にジャンプできるようになりました。

Foo.cppMain.cppファイルを含めた場合、クラスFooの定義は2つになります。リンカはこれを見て、「どれを選択すればよいかわからないので、これはエラーです」と言います。コンパイル手順は成功しますが、リンクは成功しません。 (Foo.cppをコンパイルしないだけでなく、なぜ別の.cppファイルにあるのですか?)

最後に、異なるファイルタイプの考え方は、C/C++コンパイラには関係ありません。希望する言語の有効なコードを含む「テキストファイル」をコンパイルします。ファイル拡張子に基づいて言語を判別できる場合もあります。たとえば、コンパイラオプションなしで.cファイルをコンパイルすると、Cを想定しますが、.ccまたは.cpp拡張は、C++を想定するように指示します。ただし、.hまたは.docxファイルをC++としてコンパイルするようにコンパイラーに簡単に指示できます。有効なC++コードがプレーンテキストで含まれている場合、オブジェクト(.o)ファイルが生成されますフォーマット。これらの拡張は、プログラマーの利益のためのものです。 Foo.hおよびFoo.cppが表示された場合、最初のクラスにはクラスの宣言が含まれ、2番目のクラスには定義が含まれているとすぐに想定します。

30
user22815

概念的にはCまたはC++コンパイラの最初の「フェーズ」である CおよびC++プリプロセッサ の役割について詳細をお読みください(歴史的には、これは別個のプログラム/lib/cppでしたが、今はパフォーマンスのためにコンパイラー内部に統合されている理由cc1またはcc1plus)。特にGNU cpp preprocessor のドキュメントを読んでください。したがって、実際には、コンパイラーは概念的に最初にコンパイルユニット(または translation unit )次に、前処理されたフォームで作業します。

ヘッダーファイルfile.hが含まれている場合は、常にヘッダーファイルを含める必要があります(規則および習慣):

  • マクロ定義
  • タイプ定義(例:typedefstructclassなど...)
  • static inline関数の定義
  • 外部関数の宣言。

これらをヘッダーファイルに入れるのは慣例(および便宜)の問題であることに注意してください。

もちろん、実装file.cppには上記のすべてが必要なので、最初は#include "file.h"にしたいと考えています。

これは規約です(ただし、非常に一般的なものです)。ヘッダーファイルを避け、その内容を実装ファイル(翻訳単位など)にコピーして貼り付けることができます。しかし、あなたはそれを望んでいません(おそらくCまたはC++コードが自動的に生成される場合を除いて、ジェネレータープログラムでコピーと貼り付けを行い、プリプロセッサーの役割を模倣することができます)。

ポイントは、プリプロセッサがtextualのみの操作を実行していることです。 (原則として)コピーアンドペーストで完全に回避するか、別の「プリプロセッサ」またはCコードジェネレーター( gpp または m4 など)で置き換えることができます。

追加の問題は、最近のC(またはC++)標準でいくつかの標準ヘッダーが定義されていることです。ほとんどの実装はこれらの標準ヘッダーを(実装固有)filesとして実際に実装しますが、準拠する実装が可能であると思います標準インクルード(Cの場合は#include <stdio.h>、C++の場合は#include <vector>など)を実装するには、コンパイラーの内部(---)にデータベースや情報を使用します )。

[〜#〜] gcc [〜#〜] コンパイラ(例:gccまたはg++)を使用している場合、-Hフラグを使用して情報を得ることができますすべての包含、および前処理されたフォームを取得するための-C -Eフラグ。もちろん、前処理に影響する他の多くのコンパイラフラグがあります(たとえば、インクルードファイルを検索するために-I /some/dir/を追加するには/some/dir/、プリプロセッサマクロなどを事前定義するには-Dなど)。

NB。 C++の将来のバージョン(おそらく C++ 2 、おそらくそれ以降)には C++モジュール が含まれる可能性があります。

C++の複数ユニットビルドモデルにより、プログラムに1回だけ現れるコード(定義)を作成する方法と、プログラムの各翻訳単位(宣言)にコードを表示する方法が必要です。

これから、C++ヘッダーイディオムが生まれます。それは理由による慣習です。

あなたはcanプログラム全体を単一の変換ユニットにダンプしますが、これによりコードの再利用、ユニットテスト、モジュール間の依存関係の処理に問題が生じます。それもまた大きな混乱です。

ヘッダーファイルを作成する必要があるのはなぜですか? から選択した回答は妥当な説明ですが、詳細を追加したかったのです。

ヘッダーファイルの合理性は、C/C++の指導と議論で迷子になる傾向があるようです。

ヘッダーファイルは、2つのアプリケーション開発問題の解決策を提供します。

  1. インターフェースと実装の分離
  2. 大規模プログラムのコンパイル/リンク時間の改善

C/C++は、小さなプログラムから非常に大きな数百万行、数千のファイルプログラムまで拡張できます。アプリケーション開発は、1人の開発者のチームから数百人の開発者にまで拡大できます。

開発者として複数の帽子を着用できます。特に、関数やクラスへのインターフェースのユーザーになったり、関数やクラスのインターフェースの作成者になったりできます。

関数を使用している場合、関数インターフェイス、使用するパラメーター、関数が返すもの、および関数の機能を知る必要があります。これは、実装を見ることなくヘッダーファイルに簡単に文書化できます。 printfの実装をいつ読みましたか?毎日使ってますね。

あなたがインターフェースの開発者であるとき、帽子は他の方向を変えます。ヘッダーファイルは、パブリックインターフェイスの宣言を提供します。ヘッダーファイルは、このインターフェイスを使用するために別の実装が必要とするものを定義します。この新しいインターフェイスの内部およびプライベートの情報は、ヘッダーファイルで宣言されていません(宣言してはいけません)。パブリックヘッダーファイルは、誰でもモジュールを使用するために必要なすべてのものでなければなりません。

大規模な開発では、コンパイルとリンクに時間がかかる場合があります。数分から数時間(さらには何日も!)ソフトウェアをインターフェイス(ヘッダー)と実装(ソース)に分割すると、すべてを再構築するのではなく、コンパイルする必要があるファイルのみをコンパイルする方法が提供されます。

さらに、ヘッダーファイルを使用すると、開発者はライブラリ(コンパイル済み)とヘッダーファイルを提供できます。ライブラリの他のユーザーは、適切な実装を見ることはないかもしれませんが、それでも、ヘッダーファイルでライブラリを使用できます。これは、C/C++標準ライブラリを使用して毎日行います。

小さなアプリケーションを開発している場合でも、大規模なソフトウェア開発手法を使用することは良い習慣です。ただし、これらの習慣を使用する理由も覚えておく必要があります。

0
Bill Door

すべてのコードを1つのファイルに格納しないのはなぜでしょうか。

最も簡単な答えは、コードのメンテナンスです。

クラスを作成することが妥当な場合があります。

  • ヘッダー内にすべてインライン
  • すべてコンパイル単位内にあり、ヘッダーはまったくありません。

ヘッダーに完全にインライン化するのが妥当な時期は、クラスが実際にはいくつかの基本的なゲッターとセッターと、メンバーを初期化するために値をとるコンストラクターを持つデータ構造体であるときです。

(すべてインライン化する必要があるテンプレートは、少し異なる問題です)。

ヘッダー内にすべてクラスを作成するもう1つのタイミングは、複数のプロジェクトでクラスを使用する可能性があり、特にライブラリでのリンクを回避する必要がある場合です。

コンパイル単位内にクラス全体を含め、そのヘッダーをまったく公開しない可能性があるのは、次のような場合です。

  • 実装するクラスだけが使用する「impl」クラス。これはそのクラスの実装の詳細であり、外部では使用されません。

  • 基本クラスへのポインター/参照/スマートポインターを返す何らかのファクトリーメソッドによって作成される抽象基本クラスの実装。ファクトリメソッドはクラス自体によって公開されますが、公開されません。 (さらに、クラスに、静的インスタンスを介してそれ自体をテーブルに登録するインスタンスがある場合、ファクトリを介して公開する必要さえありません)。

  • 「ファンクター」タイプのクラス。

つまり、誰にもヘッダーを含めたくない場合です。

私はあなたが何を考えているのか知っています...保守性のためだけなら、cppファイル(または完全にインライン化されたヘッダー)を含めることで、ファイルを簡単に編集してコードを「見つけ」、再構築することができます。

ただし、「保守性」とは、コードを整然と見せるだけではありません。それは変化の影響の問題です。ヘッダーを変更せずに実装を変更しただけの場合、一般的には(.cpp)ファイル。副作用がないため、他のソースを再構築する必要はありません。

これにより、ノックオン効果を心配せずにそのような変更を行うことが「安全」になり、それが「保守性」が実際に意味することです。

0
CashCow