パソコンでプログラミングしよう ウィンドウズC++プログラミング環境の構築
1.6.3.6(15)
インクルードファイル

インクルードファイルはソースコード形式をしていてコンパイラが別のソースコードファイルの一部として自動的に展開する。

その他の外部情報

本サイトでの解釈

"インクルードファイル"と"ヘッダファイル"は同じ意味と見なされ区別なく使われる。C++規格標準ライブラリ利用でインクルードするファイルを"ヘッダ(header)"(JTC1/SC22/WG21 N4659 20.5.1.2)、それ以外を"ソースファイルのインクルード(Source file inclusion)"(19.2)と呼ぶ(5.1/p1)。本サイトは"インクルードファイル"に統一するが、"ヘッダオンリーライブラリ"や"プリコンパイル済みヘッダ"などは例外とする。ソースコードの書かれた一つのファイルとそのファイルがインクルードする全てのファイルで一つの翻訳単位(translation unit)(5.1/p1)を構成する。本サイトは"ソースコードの書かれたファイル"を"ソースコードファイル"として参照する。

#include指令

プリプロセッサはソースコード中の行頭#で始まるプリプロセッサ指令を処理してコンパイラに結果を渡す。mingw-w64はプリプロセッサを狭義のコンパイラに統合してその存在をほとんど意識しないが、-Eオプションで出力を得ることができる。#include指令はファイル名を指定してそのファイルをソースコード内にインクルードする。プリプロセッサは乱暴な機械的操作を行い、#includeもインクルードファイルの拡張子、文法的な正当性、さらに文字コードさえ無視してそのまま展開する。

#includeは山括弧で囲む書式(#include <h-char-sequence>)とダブルコーテーションで囲む書式(#include "q-char-sequence")がある(N4659 19.2/p2, 19.2/p3)。h-char-sequenceとq-char-sequenceは微妙な定義差があるものの共にファイル名と考えて良く、mingw-w64実装では探索パス(インクルードディレクトリ)の違いだけである(GCC 13.2.0 Manual 3.16 Options for Directory Search)。

探索順 #include <...> #include "..." 備考
1 (スキップ) ソースコードのあるディレクトリ -
2 (スキップ) -iquoteオプション -
3 -Iオプション -
4 -isystemオプション システムヘッダ
5 標準システムディレクトリ システムヘッダ
6 -idirafterオプション システムヘッダ

標準システムディレクトリはMSYS2のmsys32/msys64ターミナルで以下に確認できる。標準システムディレクトリと-isystem/-idirafterオプション指定のディレクトリからインクルードするファイルは"システムヘッダ"としてデフォルトでウォーニング出力しない。

user@THINKPAD-L430 MINGW32 ~
$ g++ -Wp,-v -x c++ -fsyntax-only /dev/null
ignoring duplicate directory "C:/msys64/mingw32/lib/gcc/../../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include/c++/10.2.0"
ignoring duplicate directory "C:/msys64/mingw32/lib/gcc/../../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include/c++/10.2.0/i686-w64-mingw32"
ignoring duplicate directory "C:/msys64/mingw32/lib/gcc/../../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include/c++/10.2.0/backward"
ignoring duplicate directory "C:/msys64/mingw32/lib/gcc/../../lib/gcc/i686-w64-mingw32/10.2.0/include"
ignoring nonexistent directory "C:/D/users/ether/build/mingw32/include"
ignoring nonexistent directory "/mingw32/include"
ignoring duplicate directory "C:/msys64/mingw32/lib/gcc/../../lib/gcc/i686-w64-mingw32/10.2.0/include-fixed"
ignoring duplicate directory "C:/msys64/mingw32/lib/gcc/../../lib/gcc/i686-w64-mingw32/10.2.0/../../../../i686-w64-mingw32/include"
ignoring nonexistent directory "C:/D/users/ether/build/mingw32/i686-w64-mingw32/include"
#include "..." search starts here:
#include <...> search starts here:
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include/c++/10.2.0
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include/c++/10.2.0/i686-w64-mingw32
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include/c++/10.2.0/backward
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/include
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/../../../../include
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/include-fixed
C:/msys64/mingw32/bin/../lib/gcc/i686-w64-mingw32/10.2.0/../../../../i686-w64-mingw32/include
End of search list.

翻訳単位

C言語は一つのソースコードファイル(例えばA.c)とそのインクルードファイル全てで一つの翻訳単位を構成する。狭義のコンパイラは一つの翻訳単位から一つのオブジェクトファイルへ翻訳し、リンカは複数のオブジェクトファイルから一つの実行形式ファイルへ結合する。オブジェクトファイルは使用する変数/関数に関する情報をシンボルテーブルに保持し、リンカはシンボルテーブルを頼りに真の参照(アドレス)にマップする。別の翻訳単位(すなわち外部参照)の変数/関数をシンボルテーブルに登録するには正確な情報(名前や型など)が必要で、これをプログラムの全翻訳単位で一貫させなければならない。その手段としてソースコードファイル(例えばB.c)と同名のインクルードファイル(B.h)を用意し、外部へ公開する変数/関数をB.hで宣言する。A.cはB.hをインクルードしてB.cの定義する変数/関数の正確な情報を知る。B.hを公開インターフェース、B.cを内部ロジック実装と位置付ければ役割分担が明確となり、黎明より普遍的に使われる手法となった。インクルードファイルをこのように意味付けるのは常套であるものの、実体は#include指令の乱暴なテキスト挿入に変わりなく油断すると思わぬ失敗につながる。

C++はこの側面をさらに強化する。クラスはオブジェクトファイル/シンボルテーブルに情報として残らないため利用する翻訳単位全てがその定義を知る必要がある。クラス定義をメンバ変数宣言とメンバ関数宣言の集約と見なせばこれは当然の帰結であろう。非スタティックなメンバ変数は宣言が定義を兼ねるがスタティックメンバ変数とメンバ関数は先の変数/関数と同様の定義を必要とする。最も標準的な手法はクラスXに対してインクルードファイル(例えばX.h)がクラスを定義し、ソースコードファイル(X.cpp)がスタティックメンバ変数とメンバ関数を定義する。クラスが言わば公開インターフェースに相当し、他の翻訳単位はそのインスタンスを構築して利用する。統合開発環境のウィザードはCode::Blocksを含め例外なくクラスをそういったインクルード(*.h)/ソースコード(*.cpp)ファイルペアで自動生成する。privateメンバなど他の翻訳単位に不要な情報が晒されるが、その回避はpimplイディオムといった手法を必要とする。

覚え書き
ここで言う変数/関数/クラスは他の翻訳単位から利用する変数/関数/クラスであり、ファイルスコープ(*.cppの中)や関数スコープ(N4659 6.3.5)での定義はこの議論と関係しない。

インライン関数とテンプレートもオブジェクトファイル/シンボルテーブルに情報として残らず利用する翻訳単位全てがその定義を知る必要がある。特にテンプレートは総称プログラミングが多用するが定義を晒さざるを得ないケースが多く、インターフェース/ロジック実装の分離と相性が悪い。

宣言と定義

このようにインクルードファイルを公開インターフェースとする場合は"宣言(declaration)"と"定義(definition)"を使い分けるが、両者はかなり面倒な概念で完全な理解は容易でない(N4659 6.1)。宣言は様々なエンティティ(変数、関数、クラス、テンプレートなどプログラミングで名前付けできるもの全て)の名前をプログラムへ導入する。定義は宣言の一種で、同じ宣言は何度でも繰り返せるが定義は翻訳単位でただ一度しか行えない(6.2/p1)。さらにインラインでない関数、メンバでない変数、スタティックメンバ変数は、未使用でない限り(より正確にはodr-usedであれば)プログラムを通してただ一度しか定義できない(6.2/p4)。翻訳単位で定義するエンティティはプログラムを通して複数の定義が存在できるが、それらは厳密に一致しなければならない(6.2/p6)。これらのルールを総称してODR(One Definition Rule)と呼ぶ。宣言と定義はエンティティによって構文が異なり(6.1/p2)、用法もプログラミングスタイルに依存する部分が大きい。本サイトの主な用法を示すが、一般に無数の用法が存在し完全なリストは不可能に思われる。

エンティティ (定義でない)宣言 定義 ODR範囲
メンバでない変数 extern int val; int val=0; プログラム
インラインでない自由関数 void func(); void func() {} プログラム
インライン自由関数 inline void func(); inline void func() {} 翻訳単位
クラス class X; class X {}; 翻訳単位
スタティックでないメンバ変数 (無し) class X {int val_;}; 翻訳単位
スタティックメンバ変数 class X {static int val_;}; void X::val_=0; プログラム
メンバ関数 class X {void func();}; void X::func() {} プログラム
インラインメンバ関数 class X {inline void func();}; inline void X::func() {} 翻訳単位
(無し) class X {void X::func() {}}; 翻訳単位
関数テンプレート template<typename T> void func(); template<typename T> void func() {} 翻訳単位
クラステンプレート template<typenameT> class X; template<typename T> class X {}; 翻訳単位

ODR範囲の違いはエンティティがオブジェクトファイル/シンボルテーブルに情報として残るか否かの差で、ほとんどの実装はC言語とメモリ配置互換のエンティティのみを情報に残す。クラスはシンボルテーブルに情報として残らず翻訳単位で定義する。定義前に(定義でない)宣言をする場合を先行宣言と呼ぶ事があり、先行宣言されたクラスは定義されるまで不完全型(incomplete type)である(6.9/p5)。不完全型はそのポインタを定義できる(6.9/p6)だけでなく、(定義でない)変数宣言と(定義でない)関数宣言に用いる事ができる。不完全型による宣言は規格の複数個所(10.1.1/p7, 11.3.5/p11, 8.2.2/p4)で暗黙に示されるのみで不完全型ポインタほどには知られていない(Scott Meyers, Effective C++, Reading, Addison-Wesley, 1997, p.147)。pimplイディオムは不完全型ポインタを利用するテクニックの一つである。テンプレートは暗黙的/明示的特殊化でさらに複雑化して、テンプレートを考察するにまとめる。

class X; // クラスX宣言(不完全型)
extern X x1; // 不完全型変数宣言
X* px1=nullptr; // 不完全型ポインタ定義
X func1(X x); // 不完全型による関数宣言
//X x2; // 不完全型変数定義、エラー
//X* px2=new X{} // 不完全型ポインタの構築、エラー
//X func2(X x) {return x;} // 不完全型による関数定義、エラー
class X{}; // クラスX定義(完全型)
X x3; // 完全型変数定義
X* px3=new X{}; // 完全型ポインタの構築
X func3(X x) {return x;} // 完全型による関数定義

インクルード/ソースコードを翻訳単位のインターフェース/ロジック実装とする場合、公開するエンティティのODR範囲がプログラムであればインクルードファイルで(定義でない)宣言をして、ソースコードファイルで定義する。公開するエンティティのODR範囲が翻訳単位であればインクルードファイルで定義する。

#includeの用法

#include指令より前は他の#include、コメント、インクルードガード(インクルード重複を防ぐマクロ)を除き何も置かない。インクルードするファイルの使うマクロを定義する場合も#define指令の前置より統合開発環境のプロジェクト/ターゲットオプション設定を原則とする。#includeの順番に定まった規範は無くコンパイルさえ通れば良い。クラスXの翻訳単位を例に本サイトが概ね採用するスタイルを示す。

X.h

先行宣言して不完全型のままコンパイルできるクラスでも、pimplイディオムなど公開を意図しない場合を除き、それを定義する公開インターフェース(例えば<lib1>)をインクルードする。さもなければ他の翻訳単位(クライアント)が不完全型を持つインターフェースを利用する際に<lib1>をインクルードしなければならない。なお不完全型のまま<lib1>は敢えてクライアントにインクルードさせる方が良いとの主張もある(Scott Meyers, Effective C++, Reading, Addison-Wesley, 1997, p.148)。

#ifndef X_H // インクルードガード
#define X_H // インクルードガードマクロ定義
#include "A.h" // クラスXの定義が必要とする他の翻訳単位の公開インターフェース
#include <lib1> // クラスXの定義が必要とするライブラリの公開インターフェース
#include <lib2> // 〃
class X
{
...
};
#endif // X_H

X.cpp

"X.h"のインクルードを先頭とする。インクルードファイルは他のインクルードファイルに依存しないのが一つの規範で、"X.h"を先頭に置いてコンパイルして保証する。他の翻訳単位のインクルードもこれに準じてライブラリより前に配置する。

#include "X.h" // 自らの公開インターフェース
#include "B.h" // クラスXメンバ関数の定義が必要とする他の翻訳単位の公開インターフェース
#include "C.h" // 〃
#include <lib3> // クラスXメンバ関数の定義が必要とするライブラリの公開インターフェース
#include <lib4> // 〃
X::X(...)
{
...
}
...

#includeの用法(プリコンパイル済みヘッダのある場合)

プリコンパイル済みヘッダはコンパイルの高速化を目的にインクルードファイルを前処理(プリコンパイル)する事で、mingw-w64を含む多くのコンパイラが機能として持つ。プリコンパイル済みヘッダpch.hを持つ場合に本サイトが概ね採用するスタイルを示す。ライブラリインクルードをpch.hに一元化し、全ての翻訳単位ソースコードファイルがこれをインクルードする。それぞれの翻訳単位はpch.hを通じて不要なファイルを多数インクルードするが、これはより多くのインクルードをプリコンパイルしたほうがより高速化を期待できるためである。なおこういった処置は通常なら逆にコンパイル時間を増大するものとして避けるべきとされる。

pch.h

#ifndef PCH_H // インクルードガード
#define PCH_H // インクルードガードマクロ定義
#include <lib1> // 翻訳単位の少なくとも一つが必要とするライブラリの公開インターフェース
#include <lib2> // 〃
#include <lib3> // 〃
#include <lib4> // 〃
#include <lib5> // 〃
#include <lib6> // 〃
#include <lib7> // 〃
...
#endif // PCH_H

X.h

ライブラリインクルードはpch.hへ移し全て削除する。敢えて残して依存を明示するのも良い考えであるが、無用な混乱を招く場合があり削除してしまう方が潔い。

#ifndef X_H // インクルードガード
#define X_H // インクルードガードマクロ定義
#include "A.h" // クラスXの定義が必要とする他の翻訳単位の公開インターフェース
class X
{
...
};
#endif // X_H

X.cpp

mingw-w64は先頭だけをプリコンパイル済みヘッダと認識するため、"X.h"でなく"pch.h"を先頭に置く。ライブラリインクルードはpch.hへ移し全て削除する。

#include "pch.h" // プリコンパイル済みヘッダ
#include "X.h" // 自らの公開インターフェース
#include "B.h" // クラスXメンバ関数の定義が必要とする他の翻訳単位の公開インターフェース
#include "C.h" // 〃
X::X(...)
{
...
}
...