C++のヘッダーファイルとは、ほかのソースファイルから使うために必要な情報をまとめておくファイルです。
一般的には .h や .hpp という拡張子が使われます。
C++では、プログラムを1つの大きなファイルにまとめるのではなく、複数のファイルに分けて作ることがよくあります。
そのとき、あるファイルで作った関数やクラスを別のファイルから使うには、「こういう名前のものが存在します」「こういう使い方ができます」という情報を共有しなければなりません。
その共有のために使われるのがヘッダーファイルです。
まずは一番シンプルな理解
C++では、ふつう次のように役割を分けます。
- 実際の処理や中身を書くファイル
- 外部に見せる情報を書くファイル
このうち、外部に見せる情報を書く側がヘッダーファイルです。
つまりヘッダーファイルは、「ほかのファイルに公開する情報の置き場」だと考えるとわかりやすいです。
ヘッダーファイルに書かれるもの
ヘッダーファイルには、ほかのファイルがその機能を使うために必要な情報が書かれます。
代表的なものとしては、関数の宣言、クラスの定義、構造体や列挙型の定義、型エイリアス、定数、テンプレート、前方宣言などがあります。
ここで大切なのは、ヘッダーファイルには宣言だけが入るとは限らないということです。
初心者向けには「ヘッダーファイルには宣言を書く」と説明されることが多いのですが、実際にはテンプレートやインライン関数のように、定義そのものをヘッダーに書くことも普通にあります。
そのため、より正確には、ヘッダーファイルは「ほかの翻訳単位から必要になる宣言や、共有してよい定義をまとめるファイル」といえます。
宣言と定義の違い
ヘッダーファイルを理解するには、「宣言」と「定義」の違いがとても重要です。
宣言とは、「その名前のものが存在する」とコンパイラに知らせることです。
一方で定義とは、そのものの実体や中身を与えることです。
この違いを意識すると、なぜヘッダーファイルと実装ファイルを分けるのかが見えやすくなります。
使う側のファイルは、まず「何が使えるのか」を知る必要があります。
しかし、毎回その内部実装まで全部知る必要があるとは限りません。
そのため、外から見せる情報はヘッダーファイルに置き、詳細な処理は別ファイルに分けるという設計がよく使われます。
ヘッダーファイルの役割
ヘッダーファイルの役割は、大きく分けるといくつかあります。
1つ目は、複数のファイルのあいだで情報を共有することです。
関数やクラスを別ファイルから使えるようにするには、その存在や形を知らせなければなりません。ヘッダーファイルはその橋渡しをします。
2つ目は、実装と公開情報を分けることです。
使う側にとって必要なのは「どう使うか」であって、「内部でどう実装されているか」ではない場合が多くあります。
ヘッダーを使うと、この二つを分離しやすくなります。
3つ目は、再利用しやすくすることです。
同じ機能を複数の場所から使いたいとき、ヘッダーファイルを通して共通の情報を参照できるようにすると、コードの重複を減らせます。
4つ目は、保守しやすくすることです。
プログラムが大きくなると、すべてをひとつのファイルに書くやり方では管理が難しくなります。ヘッダーと実装を分けることで、構造が整理されます。
includeとは何か
ヘッダーファイルは通常、別のファイルから読み込んで使います。
この読み込みは、コンパイル前の段階で行われる仕組みによって処理されます。
感覚的には、ヘッダーファイルの内容を「その場に展開する」ような動きだと考えるとわかりやすいです。
かなり単純化して言えば、テキストを貼り付けるのに近いイメージです。
この仕組みがあるため、ヘッダーファイルに何を書くかはとても重要です。
軽い気持ちで何でも書いてしまうと、複数のファイルに同じ内容が展開され、思わぬ問題につながることがあります。
標準ライブラリのヘッダーも同じ発想
C++の標準ライブラリにもヘッダーがあります。
文字列、配列、入出力、アルゴリズムなどを使うときには、それぞれに対応したヘッダーを読み込みます。
これは、自分で作るヘッダーファイルと本質的には同じ考え方です。
必要な機能についての宣言や定義を見えるようにして、その機能を使える状態にするわけです。
クラスとヘッダーファイルの関係
C++では、クラスを使うときにヘッダーファイルが非常に重要です。
従来のC++では、クラスの完全な定義をヘッダーファイルに置くのが一般的です。
なぜかというと、クラスを値として使ったり、オブジェクトを作ったり、メンバ関数を呼び出したりするためには、その型について十分な情報が必要になることが多いからです。
そのため、クラスの構造や公開される操作はヘッダーに書かれることが多くなります。
ただし、これは絶対のルールではありません。
実装を隠したい場合には、内部構造を見せずに済ませる設計もあります。
したがって、より正確には、クラス定義をヘッダーに置くのが一般的だが、設計によっては内部を隠す方法もあるという理解が適切です。
前方宣言とは何か
ヘッダーファイルでは、毎回すべてを読み込むのではなく、名前だけ先に知らせることがあります。
これが前方宣言です。
前方宣言は、「そのクラスや型が存在する」ということだけを伝える書き方です。
中身まではまだ知らせません。
これは、依存関係を軽くしたいときにとても役立ちます。
たとえば、あるクラスが別のクラスへのポインタや参照だけを持つ場合、その相手の詳しい中身まではその場で必要ないことがあります。
そのような場合は前方宣言だけで済ませられることがあります。
前方宣言で足りる場合と足りない場合
前方宣言で十分なのは、その型について名前だけわかればよい場合です。
代表的なのは、ポインタや参照として扱う場面です。
一方で、その型の大きさや内部構造が必要になる場面では、前方宣言だけでは足りません。
その型を値として保持する場合や、オブジェクトを生成する場合、内部のメンバにアクセスする場合などでは、完全な定義が必要になります。
つまり、名前だけ知ればよいなら前方宣言でよいことがある中身まで必要ならヘッダーを読み込んで完全定義が必要になるということです。
なぜヘッダーに何でも書いてはいけないのか
ヘッダーファイルは複数のソースファイルから読み込まれることがあります。
そのため、そこに普通の関数や変数の定義を無造作に書くと、同じ定義が複数の翻訳単位に現れてしまうことがあります。
その結果、リンク時に多重定義の問題が起きることがあります。
特に注意が必要なのは、外部リンケージを持つ通常の定義です。
一方で、ヘッダーに置くことが前提になっているものもあります。
たとえばテンプレートは、使う側から定義が見えている必要があることが多いため、ヘッダーに書かれるのが一般的です。
また、インライン関数や一部の定数もヘッダーに書かれることがあります。
このように、ヘッダーには何でも書いてよいわけではなく、複数の翻訳単位に現れても問題にならないものを中心に置くという考え方が大切です。
テンプレートがヘッダーに書かれる理由
テンプレートは、通常の関数やクラスとは少し事情が異なります。
テンプレートは、実際に使われる型に応じて必要な形に作られる仕組みなので、使う側からその定義が見えている必要があることが多いです。
そのため、テンプレートは宣言だけを別にして実装を隠す、というやり方が取りにくく、定義ごとヘッダーに置かれるのが一般的です。
インクルードガードとは何か
ヘッダーファイルでは、同じファイルが何度も読み込まれて問題にならないようにする工夫が必要です。
そのために使われるのがインクルードガードです。
これは、あるヘッダーファイルが同じ翻訳単位の中で複数回読み込まれても、一度だけ有効になるようにする仕組みです。
これにより、同じ宣言や定義が重複して見えてしまう問題を防ぎやすくなります。
現代の実務では、同じ目的で、より簡潔な書き方が使われることもあります。
ただし、それは標準C++そのものの機能ではなく、コンパイラ依存の拡張として広くサポートされているものです。
循環依存の問題
ヘッダーファイル同士が互いに相手を必要とするような構造になると、循環依存の問題が起こります。
特に互いに相手を値として内包するような設計は、そのままでは成立しません。
なぜなら、片方の大きさを決めるためにもう片方の完全定義が必要で、さらにその相手の大きさを決めるために元の型の完全定義が必要になってしまうからです。
このような相互依存は解決できません。
そのため、こうした場合には前方宣言を使い、ポインタや参照を通じて依存を軽くする設計がよく用いられます。
ヘッダーファイルでusing namespaceを避ける理由
ヘッダーファイルの中で名前空間全体を取り込む書き方は、通常避けるべきです。
理由は、そのヘッダーを読み込んだすべてのソースファイルに影響が広がってしまうからです。
その結果、名前の衝突や予期しない動作の原因になることがあります。
ヘッダーファイルでは、必要な名前を明示的に書くほうが安全です。
hとhppの違い
.h と .hpp に、言語仕様上の本質的な違いはありません。
どちらもヘッダーファイルとして使えます。
違いがあるとすれば、主に慣習です。
.h は昔から広く使われており、.hpp は「これはC++用のヘッダーだ」とわかりやすくするために使われることがあります。
重要なのは、どちらを使うかよりも、プロジェクト内でルールを統一することです。
グローバル変数とヘッダー
ヘッダーファイルでは、関数やクラスだけでなく、グローバル変数に関する宣言を置くこともあります。
この場合は、ヘッダーには「その変数が存在する」という宣言を書き、実体そのものは別の実装ファイルに一回だけ置くのが基本です。
これも、ヘッダーと実装を分けるという考え方の一例です。
ヘッダーオンリーライブラリとは何か
C++には、実装ファイルを持たず、ヘッダーだけで使えるライブラリがあります。
これをヘッダーオンリーライブラリと呼びます。
テンプレート、インライン関数、定数定義などは、こうした形と相性がよいため、ヘッダーだけで完結する設計が可能です。
導入しやすいという利点がありますが、そのぶんコンパイル時間が増えやすいという欠点もあります。
翻訳単位という考え方
C++では、各ソースファイルはヘッダーファイルの内容が展開されたあと、それぞれ独立した単位としてコンパイルされます。
この単位を翻訳単位と呼びます。
ヘッダーファイルの内容は、必要に応じて複数の翻訳単位に現れます。
だからこそ、ヘッダーに何を書くかがとても重要になります。
同じ定義が複数の翻訳単位に現れると問題になることがある一方で、それを前提に正しく設計されている仕組みもあります。
ヘッダーファイルの理解は、この翻訳単位の考え方と強く結びついています。
ODRとの関係
ヘッダーファイルの設計は、C++の ODR、つまり One Definition Rule と深く関係しています。
これは簡単に言えば、「定義は何でも自由に何回でも書いてよいわけではない」というルールです。
ヘッダーファイルに通常の定義を無造作に置くと、このルールに反して問題になることがあります。
一方で、インラインやテンプレートのように、複数の翻訳単位に現れることを前提にした仕組みもあります。
初心者の段階では、ヘッダーファイルには共有してよいものだけを書く必要があると押さえておけば十分です。
現代C++ではModulesもある
現在のC++では、従来のヘッダーファイル中心の仕組みを改善するために、Modules という新しい仕組みも導入されています。
これは include による単純な展開にともなう問題を減らす方向のものです。
ただし、現時点でも多くの既存コードや現場の開発ではヘッダーファイルが中心です。
そのため、C++を理解するうえでは、まずヘッダーファイルの役割をしっかり押さえることがとても重要です。
初心者向けにたとえるなら
ヘッダーファイルは、機能の「案内書」や「公開されている入口」に近いものです。
何が使えるのか、どういう形で使うのかを外部に示し、内部の詳しい実装は別に持たせる、という考え方です。
ただし、それは単なる一覧表ではありません。
必要に応じて、テンプレートやインライン関数のように、実際の定義まで含むことがあります。
そのため、ヘッダーファイルは「ほかのファイルに見せるための情報をまとめた公開用ファイル」と理解するのがいちばん実用的です。
まとめ
C++のヘッダーファイルとは、ほかのソースファイルから利用するために必要な宣言や共有すべき定義をまとめ、読み込めるようにしたファイルです。
実務的には、次のように覚えるとわかりやすいです。
- ヘッダーファイルは、外部に見せる情報を書く場所
- 実装ファイルは、実際の処理を書く場所
- ただし、テンプレートやインライン関数のように、定義をヘッダーに置くべきものもある
- ヘッダー設計では、多重定義、依存関係、循環参照、翻訳単位を意識する必要がある
いちばんシンプルにまとめるなら、ヘッダーファイルは「他のファイルに使い方を伝えるためのファイル」です。
そして、より正確に言えば、使うために必要な宣言や、共有してよい定義をまとめたファイルです。
以上、C++のヘッダーファイルについてでした。
最後までお読みいただき、ありがとうございました。
