ファイルを含めるだけで機能させることができるのに、なぜファイル.h
と.cpp
ファイルの両方を含める必要があるの.cpp
ですか?
例:file.h
包含宣言を作成し、次にfile.cpp
包含定義を作成し、両方を包含しmain.cpp
ます。
または、file.cpp
を含む包含宣言/定義(プロトタイプなし)を作成しmain.cpp
ます。
両方とも私のために働く。違いがわかりません。コンパイルとリンクのプロセスに関する何らかの洞察が役立つかもしれません。
ファイルを含めるだけで機能させることができるのに、なぜファイル.h
と.cpp
ファイルの両方を含める必要があるの.cpp
ですか?
例:file.h
包含宣言を作成し、次にfile.cpp
包含定義を作成し、両方を包含しmain.cpp
ます。
または、file.cpp
を含む包含宣言/定義(プロトタイプなし)を作成しmain.cpp
ます。
両方とも私のために働く。違いがわかりません。コンパイルとリンクのプロセスに関する何らかの洞察が役立つかもしれません。
回答:
あなたが.cpp
言及したようにファイルを含めることができますが、これは悪い考えです。
前述のように、宣言はヘッダーファイルに属します。これらは、実装が含まれていないため、複数のコンパイル単位に含まれていても問題はありません。関数またはクラスメンバーの定義を複数回含めると、通常は問題が発生します(常にではありません)。これは、リンカーが混乱してエラーをスローするためです。
起こるべきことは、各.cpp
ファイルには、クラス、論理的に編成された関数のグループ、グローバルな静的変数(あるとしても控えめに使用する)など、プログラムのサブセットの定義が含まれていることです。
各コンパイル単位(.cpp
ファイル)には、含まれる定義をコンパイルするために必要な宣言が含まれます。参照する関数とクラスを追跡しますが、含まれないため、リンカはオブジェクトコードを実行可能ファイルまたはライブラリに結合するときにそれらを後で解決できます。
例
Foo.h
->クラスFooの宣言(インターフェイス)が含まれます。Foo.cpp
-> Fooクラスの定義(実装)が含まれています。Main.cpp
-> mainメソッド、プログラムエントリポイントが含まれます。このコードはFooをインスタンス化し、それを使用します。どちらFoo.cpp
とMain.cpp
含む必要性Foo.h
。Foo.cpp
クラスインターフェイスをサポートするコードを定義しているため、そのインターフェイスが何であるかを知る必要があるため、これが必要です。Main.cpp
Fooを作成してその動作を呼び出すため、それが必要です。そのため、その動作とは何か、メモリ内のFooのサイズ、その機能を見つける方法などを知る必要がありますが、実際の実装はまだ必要ありません。
コンパイラが生成されますFoo.o
から、Foo.cpp
コンパイルされた形式でのFooクラスのすべてのコードが含まれています。またMain.o
、mainメソッドとクラスFooへの未解決の参照を含むものも生成します。
今、2つのオブジェクト・ファイルを結合したリンカー、来るFoo.o
とMain.o
実行可能ファイルには。未解決のFoo参照Main.o
は表示されFoo.o
ますが、必要なシンボルが含まれているため、いわば「点をつなぐ」のです。関数呼び出しMain.o
は、コンパイルされたコードの実際の場所に接続されるようになったため、実行時にプログラムは正しい場所にジャンプできます。
Foo.cpp
ファイルをに含めた場合、クラスFooには2つの定義Main.cpp
があります。リンカはこれを見て、「どれを選ぶべきかわからないので、これはエラーです」と言います。コンパイル手順は成功しますが、リンクは成功しません。(コンパイルしない場合を除き、なぜ別のファイルに保存されるのですか?)Foo.cpp
.cpp
最後に、さまざまなファイルタイプの概念は、C / C ++コンパイラとは無関係です。希望する言語の有効なコードを含む「テキストファイル」をコンパイルします。ファイル拡張子に基づいて言語を伝えることができる場合があります。たとえば、.c
コンパイラオプションを使用せずにファイルをコンパイルすると、Cが想定されますが、拡張子.cc
または.cpp
拡張子はC ++を想定するように指示します。しかし、私は簡単にコンパイルするコンパイラに伝えることができ.h
、さらには.docx
C ++などのファイルを、それがオブジェクト(放出する.o
ことがプレーンテキスト形式で有効なC ++コードが含まれている場合)ファイルを。これらの拡張機能は、プログラマの利益のためにあります。私が見た場合Foo.h
とFoo.cpp
、私はすぐにファーストクラスの宣言が含まれており、第二の定義が含まれていることを前提としています。
Foo.cpp
にはMain.cpp
、あなたがする必要はありません.h
ファイルを、あなたはまだ読みやすくするために別々のファイルに分割するコードで勝つ、1つの以下のファイルを持っており、あなたのコンパイルコマンドが簡単です。私は理解しますが、ヘッダーの重要性は、私はこれがそれだとは思わないファイル
If you had included the Foo.cpp file in Main.cpp, there would be two definitions of class Foo.
質問に関する最も重要な文。
概念的にはCまたはC ++コンパイラーの最初の「フェーズ」であるCおよびC ++プリプロセッサーの役割について詳しく読んでください(これまでは別個のプログラム/lib/cpp
でした。現在、パフォーマンス上の理由から、コンパイラー自体cc1
またはに統合されていますcc1plus
)。特にGNU cpp
プリプロセッサのドキュメントをお読みください。そのため、実際には、コンパイラは概念的に最初にコンパイル単位(または変換単位)を前処理してから、前処理されたフォームで作業します。
ヘッダーファイルに次の内容が含まれている場合は、常にヘッダーファイルを含める必要がありfile.h
ます(規則と習慣によって決まります)。
typedef
、struct
、class
など、...)static inline
関数の定義これらをヘッダーファイルに配置することは、慣例(および利便性)の問題であることに注意してください。
もちろん、あなたの実装file.cpp
は上記のすべてを必要とするので#include "file.h"
、最初はそうしたいです。
これは慣例です(ただし、非常に一般的なものです)。ヘッダーファイルを回避し、その内容をコピーして実装ファイル(つまり、翻訳単位)に貼り付けることができます。しかし、それは望ましくありません(おそらく、CまたはC ++コードが自動的に生成される場合を除いて、ジェネレータプログラムにそのコピー&ペーストを行わせ、プリプロセッサの役割を模倣することができます)。
ポイントは、プリプロセッサがテキストのみの操作を実行していることです。(原則として)コピー&ペーストで完全に回避するか、別の「プリプロセッサ」またはCコードジェネレーター(gppやm4など)で置き換えることができます。
追加の問題は、最近のC(またはC ++)標準が複数の標準ヘッダーを定義していることです。ほとんどの実装では、実際にこれらの実装標準(実装固有)などのヘッダファイルを、私は準拠した実装は標準実装することが(のような含みことが可能であろうと信じている#include <stdio.h>
Cのために、または#include <vector>
C ++用)など、いくつかのマジックのトリックと(いくつかのデータベースまたは一部を使用してコンパイラ内の情報)。
使用している場合のGCCコンパイラ(例えばgcc
またはg++
)をあなたが使用することができ-H
、すべてのインクルージョンについての情報を取得するフラグを、そして-C -E
フラグが前処理されたフォームを得ること。もちろん、前処理に影響する他の多くのコンパイラフラグがあります(たとえば-I /some/dir/
、/some/dir/
インクルードファイルの検索用に追加したり、-D
一部のプリプロセッサマクロなどを事前定義したりするなど)。
NB。C ++の将来のバージョン(おそらくC ++ 20、おそらくそれ以降)にはC ++モジュールが含まれる可能性があります。
C ++の複数ユニットビルドモデルのため、プログラムに1回だけ表示されるコード(定義)が必要であり、プログラムの各翻訳ユニットに表示されるコード(宣言)が必要です。
これからC ++ヘッダーのイディオムが生まれます。それには理由があります。
あなたはできる、単一の翻訳単位にあなたのプログラム全体をダンプしますが、コードの再利用、ユニットテストとモジュール間の依存関係の扱いで、この紹介の問題。それはただの大きな混乱でもあります。
なぜヘッダーファイルを記述する必要があるのかという選択した回答 合理的な説明ですが、詳細を追加したかったのです。
ヘッダーファイルの合理性は、C / C ++の教育と議論で失われる傾向があるようです。
ヘッダーファイルは、2つのアプリケーション開発の問題に対する解決策を提供します。
C / C ++は、小さなプログラムから非常に大きな数百万行、数千のファイルプログラムまで拡張できます。アプリケーション開発は、1人の開発者のチームから数百人の開発者にまで拡大できます。
開発者としていくつかの帽子をかぶることができます。特に、関数とクラスへのインターフェイスのユーザーになるか、関数とクラスのインターフェイスの作成者になることができます。
関数を使用している場合、関数インターフェース、使用するパラメーター、関数が返すもの、および関数が何を行うかを知る必要があります。これは、実装を見ることなく、ヘッダーファイルに簡単に文書化されます。の実装をprintf
いつ読みましたか?私たちは毎日それを使用して購入します。
あなたがインターフェースの開発者であるとき、帽子は他の方向を変えます。ヘッダーファイルは、パブリックインターフェイスの宣言を提供します。ヘッダーファイルは、このインターフェイスを使用するために別の実装が必要とするものを定義します。この新しいインターフェイスの内部およびプライベート情報は、ヘッダーファイルで宣言されていません(宣言すべきではありません)。パブリックヘッダーファイルは、モジュールを使用するために必要なすべてのファイルである必要があります。
大規模な開発では、コンパイルとリンクに時間がかかる場合があります。数分から数時間(偶数から数日まで!)ソフトウェアをインターフェース(ヘッダー)と実装(ソース)に分割すると、すべてを再構築するのではなく、コンパイルする必要があるファイルのみをコンパイルする方法が提供されます。
さらに、ヘッダーファイルを使用すると、開発者はライブラリ(既にコンパイル済み)とヘッダーファイルを提供できます。ライブラリの他のユーザーは、適切な実装を見ることはないかもしれませんが、ヘッダーファイルでライブラリを使用できます。これは、C / C ++標準ライブラリを使用して毎日行います。
小さなアプリケーションを開発している場合でも、大規模なソフトウェア開発手法を使用するのは良い習慣です。しかし、これらの習慣を使用する理由も覚えておく必要があります。
同様に、すべてのコードを1つのファイルに入れない理由を尋ねることもできます。
最も簡単な答えはコードのメンテナンスです。
クラスを作成するのが妥当な場合があります。
ヘッダーに完全にインライン化するのが妥当なのは、クラスが実際にいくつかの基本的なゲッターとセッターと、おそらくメンバーを初期化するための値を取るコンストラクターを持つデータ構造体であるときです。
(すべてインラインにする必要があるテンプレートは、わずかに異なる問題です)。
ヘッダー内にすべてクラスを作成するもう1つの場合は、複数のプロジェクトでクラスを使用する可能性があり、特にライブラリでのリンクを回避する必要がある場合です。
コンパイル単位内にクラス全体を含め、ヘッダーをまったく公開しない場合は次のとおりです。
実装するクラスによってのみ使用される「impl」クラス。それはそのクラスの実装の詳細であり、外部では使用されません。
基本クラスへのポインター/参照/スマートポインターを返す何らかの種類のファクトリメソッドによって作成される抽象基本クラスの実装。ファクトリメソッドはクラス自体によって公開され、公開されません。(さらに、クラスが静的インスタンスを介してテーブルに登録するインスタンスを持っている場合、ファクトリを介して公開する必要さえありません)。
「ファンクター」タイプのクラス。
つまり、誰にもヘッダーを含めたくない場合。
あなたが何を考えているのか知っています... cppファイル(または完全にインライン化されたヘッダー)をインクルードすることにより、メンテナンス性のためだけであれば、ファイルを簡単に編集してコードを「見つけ」、再構築することができます。
ただし、「保守性」とは、コードが整然と見えることだけではありません。それは変化の影響の問題です。ヘッダーを変更せずに実装(.cpp)
ファイルを変更するだけであれば、副作用がないため、他のソースを再構築する必要はないことが一般的に知られています。
これにより、ノックオン効果を心配せずにそのような変更を行うことが「安全」になります。それが実際に「維持可能性」とは何を意味するかです。
Snowmanの例では、.hファイルが必要な理由を正確に示すために、小さな拡張子が必要です。
クラスFooにも依存する別のクラスバーをプレイに追加します。
Foo.h-> Fooクラスの宣言を含む
Foo.cpp->クラスFooの定義(実装)を含む
Main.cpp-> Foo型の変数を使用します。
Bar.cpp-> Foo型の変数も使用します。
ここで、すべてのcppファイルにFoo.hを含める必要があります。他の複数のcppファイルにFoo.cppを含めるとエラーになります。クラスFooが複数回定義されるため、リンカーは失敗します。
.h
とにかくファイルで解決されるのとまったく同じ方法で解決できます- インクルードガードを.h
使用すると、とにかくあなたがファイルで持っているのとまったく同じ問題です。これは.h
ファイルの目的ではありません。
.cpp
ます。ファイルをインクルードするため、リンクはまったく行われません。