こんにちは、みにくい社長です。
今日はプリコンパイル済みヘッダについて解説します。 C++を勉強し始めたころはよくわかっていなったのでインターネットで検索して調べていたのですが、 正確に解説しているサイトが見当たらず、よくわからないまま使っていました。 同じように悩んでいる方は是非読んでみてください。
C++のコンパイル時に未定義エラーが出た時、とりあえずincludeを書いて対処することが多いと思います。 重複が多くなってきたので整理しようとしても、どの順番で読み込まれているのかわからず、動くまで試行錯誤してまた汚い状態で放置する…の繰り返しになっていたりしないでしょうか? では、基本原理から解説していきます。
インクルード順序
C++のビルドはcppをコンパイルしてobjにし、結合したものがlibやexeになります。 この時、ヘッダファイルは必ずしも必要ではありません。 ヘッダファイルはライブラリのユーザーに使用方法を伝える為、もしくは複数のcppから定義を参照(include)したい時の為に用意します。 参照される時は宣言だけがあれば良く、実装はcppファイルに記述して隠蔽します。 ここでやっかいなのが、
- 未定義の型を使用するとエラーになる
- 多重定義もエラーになる
ということです。 include文を書くと対象のヘッダファイルがそのまま展開されますが、更にそのヘッダファイルに書かれているinclude文も展開されます。 ここで、A.hがB,hをインクルードし、B.hがA.hをインクルードすると、無限に展開されてしまいます。 これを回避するため、一度展開されたファイルは2回目の参照では読み込まれないようにします(多重インクルードガード)。 手法は2種類あります。 先頭に下記の1行を追加するか、
pragma once
ファイルの最初と最後に下記の文を追加します。
#ifndef __A_H__ #define __A_H__ : #endif //__A_H__
これで多重インクルードは防げますが、それでも問題が発生します。
class A{ B* _pB; }; class B{ A* _pA; };
のように相互に参照している場合、どちらを先に記述しても未定義の型を使用することになります(相互参照)。 このような時は、classAの前に
class B;
を記述することで型の宣言だけを先に行い、未定義エラーを防ぐことができます(前方宣言)。 この程度ならなんとかなりますが、インクルードしているヘッダが増えてくると、どの順序で読み込まれているかを調べるだけでも大変になってきます。
プリコンパイル済みヘッダ
ヘッダファイルを変更してビルドをすると、ヘッダファイルを参照している全てのcppファイルが再コンパイルされます。 1つのcppファイルをコンパイルする度に関連するヘッダファイルを全て解析するので、時間もかかります。 なのでヘッダファイルのインクルードは極力減らしておく方が良いです。 しかしOSやゲームエンジンなどの膨大なプログラムとなると、参照が複雑になるのは避けられません。 そんな時に有効なのがプリコンパイル済みヘッダです。 プリコンパイル済みヘッダ(.pch)とは、解析済みのヘッダを1つにまとめたものです。 解析済みのヘッダなので、cppファイルをコンパイルする度に使用しても低負荷で済みます。 但しヘッダファイルを1つ書き換えると、プリコンパイル済みヘッダを参照しているcppは全て再コンパイルされてしまいます。 理想的な使い方は、
- 関連しているファイル群(ライブラリ)を1つのプリコンパイル済みヘッダにまとめる
- ライブラリ毎にプリコンパイル済みヘッダを作成する
となります。 ■使用方法 まず、プリコンパイル済みヘッダの元になるインクルードリストを記載したヘッダファイル(ABC.h)を用意します。
#include "A.h" #include "B.h" :
必要になるcppファイルはこのヘッダファイルのみをインクルードします。
#include "ABC.h" A::A(void){ } :
ABC.cppも用意しますが、内容はABC.hをインクルードするだけです。 この時、各ヘッダファイルにもincludeを書かないようにします。 そしてプロジェクト設定では「プリコンパイル済みヘッダー」を「使用(/Yu)」にし、ABC.cppの設定では「作成(/Yc)」とします。 後はビルドをするだけです。 今後ファイルが増えた時は、プリコンパイル済みヘッダにincludeを追加するだけです。 読み込み順序も記載している順序になりますので、問題が視覚的にわかりやすくなります。 コンパイルが速くなり、可読性も上がりました。良いことばかりですね。
では、またねー