[ C++で開発 ]

ヘッダファイルとは

C++の面倒で複雑なものの一つがヘッダファイルの扱いです。Toyプログラムでは問題にならなくとも、大きなプログラムになると、コンパイル時間が増大したり、管理が大変になったり、とやっかいな代物です。

なぜヘッダファイルがいるのか

次のコードはコンパイルエラーとなります。

int main() {
  bar();
}
barが未定義
int main() {
  Foo myFoo;
}
Fooが未定義

C++では、コンパイルで使用するシンボルが事前に宣言または定義されている必要があります。

void bar();

int main() {
  bar();
}
関数barのプロトタイプ宣言を記述
class Foo {
private:
  int value;
public:
  Foo();
  Foo(int v);
  ~Foo();
  void process();
};

int main() {
  Foo myFoo;
}
クラスFooの定義を記述

しかし、この場合bar関数やFooクラスを使用するプログラムのファイルが複数あった場合(普通は多くのファイルから利用されます)、ファイル毎に、関数barのプロトタイプ宣言やクラスFooの定義を書くことになります。それでは書くのも大変だが、メンテナンス上もっと大変なことになります。

そこで、このbar関数宣言やFooクラス定義をただ一つのファイル(ヘッダファイル)に記述し、使用するプログラムのファイルでは、ヘッダファイルをインクルードします。

Foo.h
void bar();

class Foo {
private:
  int value;
public:
  Foo();
  Foo(int v);
  ~Foo();
  void process();
};
Foo.hでは、関数barのプロトタイプ宣言とクラスFooの定義を記述している
#include "Foo.h"

int main() {
  bar();
}
関数barのプロトタイプ宣言は、Foo.hをインクルードすることでOK
#include "Foo.h"

int main() {
  Foo myFoo;
}
クラスFooの定義は、Foo.hをインクルードすることでOK

ヘッダファイル名

ヘッダファイルのファイル名は何をつければよいでしょうか。慣例では拡張子.hを使用します。しかし、C++標準ライブラリのヘッダファイルのように、拡張子がないものもあります。

#include

インクルード文には、ヘッダファイル名を""で囲む方法と、<>で囲む方法の2つがあります。

#include "Foo.h"

#include <Foo.h>

""で囲む場合、コンパイラはまず#include文を記述したソースファイルと同じカレントディレクトリにヘッダファイルがあるか探します。そこになければ、インクルードパスに指定されているディレクトリを探しにいき、そこになければシステム・インクルード・ディレクトリを探します。

<>で囲む場合、インクルードパスに指定されているディレクトリを探しにいきます。#include文を記述したソースファイルと同じカレントディレクトリは探しません。

""と<>の使い分け

システムヘッダーファイル(C/C++標準ヘッダー、OS提供ヘッダー)やサードパーティ・ライブラリのヘッダー等開発対象外のヘッダーファイルをincludeするときは<>を使用します。

開発対象ヘッダーファイルのincludeについては、

ヘッダファイルに記述すること

ヘッダファイルには、何を記述するべきなのでしょうか。C++の文法上は何を書いてもよいのですが、実際にはいろいろな問題が発生します。おおよそ、以下のものはヘッダファイルに記述すると思います。物の本には、「外部リンケージを持たないものを書く」とありました。

定数について

C言語では、プリプロセッサを使って定数を表現することが主流でした。しかし、プリプロセッサは言語仕様の外にあるため、せっかくC++で強い型チェックができるようになって安全性が増したのに、その恩恵に与れません。全文一括置換みたいなものなので非常に危険です。また、名前空間があるのに、それを飛び越してしまうのも問題です。そこで、定数の表現にはconst変数を使用します。

複数のコンパイル単位で共通に使用される定数は、ヘッダファイルに記述することになります。このとき、ポインタ型の変数を定数として使用する場合には注意が必要です。

ヘッダファイル
const int NUM_TOWER = 24;
const char *LABEL_TEXT = "Number of towers";
モジュールA
#include "tower.h"

//
モジュールB
#include "tower.h"

//

このモジュールAとモジュールBをリンクすると、LABEL_TEXTが二重定義されているというリンクエラーが発生します。これは、char型へのポインタであるLABEL_TEXT変数はconstではないため、#includeされた時点でグローバル変数の定義とみなされ、モジュールAとモジュールBとに定義が存在してリンクエラーとなります。

これを避けるためには、ポインタ型の変数をconst定義する場合はポインタが指す内容をconstにするのに加えて、ポインタ自身もconstにします。

上記の例では、char型へのポインタLABEL_TEXTをconstにする宣言を追加します。

const char *const LABEL_TEXT = "Number of towers";