プログラムの規模が大きくなってくると、ヘッダーがヘッダーをインクルードするような場面が発生してきます。このとき、複数のヘッダーをインクルードした場合に、型の再定義が発生してビルドエラーになる場合があります。
このような場合を予め想定して、インクルードガードをヘッダーファイルに挿入することで、二重インクルードによる型の再定義エラーを回避することができます。
型の再定義が発生する例
どのように型の再定義が発生するかを解説します。
以下、ソースの例です。
// src1.c
#include "src1.h"
// src2.c
#include "src2.h"
// head1.h(src1.hとsrc2.hで使用する共通の構造体を定義)
typedef struct _BASE{
int m1;
}BASE;
// src1.h
#include "head1.h"
typedef struct _ST1{
int m1;
BASE m2; // head1.hで定義している構造体
}ST1;
// src2.h
#include "head1.h"
typedef struct _ST2 {
int m1;
int m2;
BASE m3; // head1.hで定義している構造体
}ST2;
// src1.c
#include "src1.h" // コンパイルOK
// src2.c
#include "src2.h" // コンパイルOK
// main.c
#include "src1.h"
#include "src2.h" // コンパイルエラー発生!
main.cのコンパイルで以下のエラーが発生します。
error C2011: ‘_BASE’: ‘struct’ 型の再定義
コンパイルはソースファイル単位で実行されるのでsrc1.cとsrc2.cはコンパイルエラーにはなりません。
src1.c
+ src1.h
+ head1.h
→ソースファイル単位で見るとsrc1.cは型の再定義が発生していないことが分かると思います。
main.cは以下のようにヘッダーファイルがインクルードされます。
main.c
+ src1.h
+ head1.h
+ src2.h
+ head1.h
→head1.hが2回インクルードされ、型の再定義が発生していることが分かると思います。
そもそもヘッダーに依存関係を無くすべき?
型の再定義を防ぐために、「ヘッダーがヘッダーをインクルードすることを禁止するべき」、「ヘッダーファイル間に依存関係を持たせるべきではない」と言う方を見かけることがありますが、そうではありません。
依存関係を持たせないと言うことは、データ構造が煩雑になります。同じ目的で使用する構造体を複数定義することになってしまいますし、構造体に変更を加えたいときに、何か所も変更しなくてはいけないという事態になります。
データ構造を共通化させることは可読性も良くなり、変更も容易になります。
標準ライブラリもヘッダーがヘッダーをインクルードしています。これが答えになっていると思います。
インクルードガードの書き方
型の再定義を防止する方法はとても簡単です。
ヘッダーファイルの先頭と終端に以下のような記述をするだけです。
上記のhead1.hを例にインクルードガードを記述します。
#ifndef _H_HEAD1_
#define _H_HEAD1_
typedef struct _BASE{
int m1;
}BASE;
#endif // _H_HEAD1_
#ifndef _H_HEAD1_ ~ #endif は、_H_HEAD1_が定義されていない場合に読み込まれます。
読み込まれた場合は、 #define _H_HEAD1_ の記述によって_H_HEAD1_が定義されるため、この範囲は何回インクルードしても最初の1回だけ読み込まれるようになります。
インクルードガードは全てのヘッダーファイルに記述するように癖を付けましょう。
デファイン名はお好みで付けて良いですが、筆者は _H_ヘッダファイル名_にしています。
何のデファインか分かりやすいこと、他のデファイン名と被りにくいことが大事です。
Visual Studioにおけるインクルードガードの書き方
Visual Studioでは更に簡単にインクルードガードを記述することができます。
上記のhead1.hを例にインクルードガードを記述します。
#pragma once
typedef struct _BASE{
int m1;
}BASE;
ヘッダの先頭に #pragma once と記述するだけ二重インクルードを回避できます。
どちらの書き方が良いか
Visual Studioでしか開発をしないということが確定しているのであれば、#pragma onceで良いと思いますが、開発環境が変わる可能性が少しでもあるのであれば、#ifndef~#endifを使用した方法が良いです。こちらの書き方は開発環境に依存しないため、どのような開発環境でもビルドが通ります。
◆ 初学者向けの入門書
C++の機能をわかりやすく、丁寧に解説している良書です。
◆ 中級者向けの書籍
C++の開発者が書いた本なので、C++11までの仕様が網羅的に書かれています。
◆ 上級者向けの書籍
C++を効率的かつ正確に使用するための最適解とガイドラインを示してくれる良書です。
各章は、特定のテクニックやアプローチに関する詳細な説明と例を含んでおり、プログラマがより良いコードを書くために一役買ってくれます。
コメント