C ++にヘッダーファイルと.cppファイルがあるのはなぜですか?
C ++にヘッダーファイルと.cppファイルがあるのはなぜですか?
回答:
まあ、主な理由は、インターフェイスを実装から分離するためです。ヘッダーは、クラス(または実装されているもの)が「何をするか」を宣言し、cppファイルは、それらの機能を実行する「方法」を定義します。
これにより依存関係が減少するため、ヘッダーを使用するコードは必ずしも実装のすべての詳細と、そのためだけに必要なその他のクラス/ヘッダーをすべて知っている必要はありません。これにより、コンパイル時間が短縮され、実装の何かが変更されたときに必要な再コンパイルの量も削減されます。
完璧ではありません。通常、Pimpl Idiomなどの手法を使用して、インターフェイスと実装を適切に分離しますが、それは良い出発点です。
C ++でのコンパイルは、2つの主要なフェーズで行われます。
1つは、「ソース」テキストファイルをバイナリの「オブジェクト」ファイルにコンパイルすることです。CPPファイルはコンパイル済みファイルであり、生の宣言またはヘッダーの包含。CPPファイルは通常、.OBJまたは.O "オブジェクト"ファイルにコンパイルされます。
2つ目は、すべての「オブジェクト」ファイルのリンク、つまり最終的なバイナリファイル(ライブラリまたは実行可能ファイル)の作成です。
HPPはこのすべてのプロセスのどこに適合しますか?
各CPPファイルのコンパイルは、他のすべてのCPPファイルから独立しています。つまり、A.CPPがB.CPPで定義されたシンボルを必要とする場合、次のようになります。
// A.CPP
void doSomething()
{
doSomethingElse(); // Defined in B.CPP
}
// B.CPP
void doSomethingElse()
{
// Etc.
}
A.CPPには「doSomethingElse」が存在することを知る方法がないため、コンパイルは行われません... A.CPPに次のような宣言がない限り、
// A.CPP
void doSomethingElse() ; // From B.CPP
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
次に、同じシンボルを使用するC.CPPがある場合は、宣言をコピーして貼り付けます...
はい、問題があります。コピー/貼り付けは危険であり、保守が困難です。これは、コピー/貼り付けを行わずにシンボルを宣言する方法があったらすばらしいと思います...どうすればよいでしょうか?通常、.h、.hxx、.h ++、または私のC ++ファイルに推奨される.hppがサフィックスとなるテキストファイルを含めることにより、
// B.HPP (here, we decided to declare every symbol defined in B.CPP)
void doSomethingElse() ;
// A.CPP
#include "B.HPP"
void doSomething()
{
doSomethingElse() ; // Defined in B.CPP
}
// B.CPP
#include "B.HPP"
void doSomethingElse()
{
// Etc.
}
// C.CPP
#include "B.HPP"
void doSomethingAgain()
{
doSomethingElse() ; // Defined in B.CPP
}
include
ますか?ファイルを含めると、本質的には、その内容が解析され、CPPファイルにコピーアンドペーストされます。
たとえば、次のコードでは、A.HPPヘッダーを使用しています。
// A.HPP
void someFunction();
void someOtherFunction();
...ソースB.CPP:
// B.CPP
#include "A.HPP"
void doSomething()
{
// Etc.
}
...組み込み後になります:
// B.CPP
void someFunction();
void someOtherFunction();
void doSomething()
{
// Etc.
}
現在のケースでは、これは不要であり、B.HPPにはdoSomethingElse
関数宣言があり、B.CPPにはdoSomethingElse
関数定義(つまり、それ自体が宣言)があります。ただし、B.HPPが宣言(およびインラインコード)に使用される、より一般的なケースでは、対応する定義(たとえば、列挙型、プレーンな構造体など)がない可能性があるため、B.CPP B.HPPからの宣言を使用します。全体として、ソースにデフォルトでヘッダーを含めるのは「いい味」です。
C ++コンパイラはシンボル宣言だけを検索できないため、ヘッダーファイルが必要です。したがって、これらの宣言を含めることで、ヘッダーファイルを支援する必要があります。
最後に一言:HPPファイルのコンテンツの周りにヘッダーガードを配置して、複数のインクルードによって何も破壊されないようにする必要がありますが、全体として、HPPファイルが存在する主な理由は上記で説明されていると思います。
#ifndef B_HPP_
#define B_HPP_
// The declarations in the B.hpp file
#endif // B_HPP_
またはさらに簡単に
#pragma once
// The declarations in the B.hpp file
You still have to copy paste the signature from header file to cpp file, don't you?
必要ありません。CPPがHPPを「含む」限り、プリコンパイラーはHPPファイルの内容をCPPファイルに自動的にコピーアンドペーストします。私はそれを明確にするために答えを更新しました。
While compiling A.cpp, compiler knows the types of arguments and return value of doSomethingElse from the call itself
。いいえ、ありません。ユーザーから提供された型しか認識しないため、半分の時間で戻り値を読み取る必要さえありません。次に、暗黙的な変換が行われます。そして、あなたがコードを持っているとき:foo(bar)
、あなたはfoo
関数であることを確信することさえできません。そのため、コンパイラーは、ソースが正しくコンパイルされるかどうかを判断するためにヘッダー内の情報にアクセスする必要があります...次に、コードがコンパイルされると、リンカーは関数呼び出しをリンクするだけです。
Seems, they're just a pretty ugly arbitrary design.
:C ++が2012年に作成された場合は、確かに。ただし、C ++は1980年代にC上に構築され、当時の制約はまったく異なっていたことを思い出してください(IIRC、採用目的でCと同じリンカーを維持することが決定されました)。
foo(bar)
が関数であるのか確信が持てないのですか?それがポインタとして取得された場合?実際、悪いデザインと言えば、C ++ではなくCのせいです。純粋なCのいくつかの制約(ヘッダーファイルや関数が1つだけの値を返すなど)が好きではありませんが、入力で複数の引数を取ります(入力と出力が同じように動作するのは当然ではないでしょうか)。 ;なぜ複数の引数が1つの出力か?):)
Why can't I be sure, that foo(bar) is a function
fooは型である可能性があるため、クラスコンストラクターが呼び出されます。In fact, speaking of bad design, I blame C, not C++
:私は多くのことでCのせいにすることができますが、70年代に設計されたことはそれらの1つではありません。繰り返しになりますが、その時間の制約... such as having header files or having functions return one and only one value
:タプルは、それを緩和するのに役立ち、参照によって引数を渡すこともできます。さて、返された複数の値を取得するための構文は何でしょうか。言語を変更する価値はありますか?
コンセプトの発端となったCは30年前のもので、複数のファイルからコードをリンクする唯一の実行可能な方法でした。
今日、それはC ++でのコンパイル時間を完全に破壊し、無数の不必要な依存関係を引き起こします(ヘッダーファイルのクラス定義が実装に関する情報を公開しすぎているため)など、ひどいハックです。
ライブラリ形式を設計した人々は、Cプリプロセッサマクロや関数宣言などのほとんど使用されない情報のためにスペースを「無駄に」したくなかったからです。
コンパイラに「この関数は後でリンカがその仕事をしているときに利用できる」と伝えるためにその情報が必要なので、共有情報を格納できる2つ目のファイルを用意する必要がありました。
C / C ++以降のほとんどの言語は、この情報を出力(Javaバイトコードなど)に格納するか、プリコンパイル済みフォーマットをまったく使用せず、常にソース形式で配布され、オンザフライでコンパイルされます(Python、Perl)。
多くの場合、コード全体を出荷する必要なく、インターフェースの定義が必要になります。たとえば、共有ライブラリがある場合、共有ライブラリで使用されるすべての関数とシンボルを定義するヘッダーファイルを同梱します。ヘッダーファイルがない場合は、ソースを出荷する必要があります。
単一のプロジェクト内では、少なくとも2つの目的でヘッダーファイルIMHOが使用されます。
MadKeithVの回答に応えて、
これにより依存関係が減少するため、ヘッダーを使用するコードは必ずしも実装のすべての詳細と、そのためだけに必要なその他のクラス/ヘッダーをすべて知っている必要はありません。これにより、コンパイル時間が短縮され、実装の何かが変更されたときに必要な再コンパイルの量も削減されます。
もう1つの理由は、ヘッダーが各クラスに一意のIDを与えることです。
だから私たちのようなものがある場合
class A {..};
class B : public A {...};
class C {
include A.cpp;
include B.cpp;
.....
};
AはBの一部であるため、プロジェクトをビルドしようとするとエラーが発生します。ヘッダーを使用すると、この種の頭痛の種を回避できます...