【C++】インクルードガード(#ifndef)とは?|#pragma onceとの違いも解説

サムネイル画像 C++

今回は、C++でヘッダーファイルを書く時によく出てくる「インクルードガード(#ifndef)」について解説していきます。

#ifndef って何のために書くの?」
#pragma once と何が違うの?」
「ヘッダーファイルの先頭に毎回あるけど、正直よく分からない…」

こんな疑問はありませんか?

C++でクラスや関数をヘッダーファイルに分け始めると、かなり高い確率で出てくるのがインクルードガードです。最初は定型文のように見えますが、これを理解していないと再定義エラー意味の分かりづらいコンパイルエラーでかなりハマりやすくなります。

この記事を読み終えると、あなたはインクルードガードの意味・なぜ必要なのか・#pragma once との違い・初心者がやりがちなミスをしっかり理解できるようになると思いますので、ぜひ最後まで読んでいただけると嬉しいです。

インクルードガード(#ifndef)とは?

インクルードガードとは、同じヘッダーファイルが1つの.cppファイルの中で複数回読み込まれるのを防ぐ仕組みです。

C++では、ヘッダーファイルを #include すると、その中身がその場に展開されるイメージです。もし同じヘッダーが何度も展開されると、クラス定義や関数宣言が重複して、再定義エラーの原因になります。

そこで使うのが、次のような #ifndef#define#endif の組み合わせです。

▼Player.h


#ifndef PLAYER_H
#define PLAYER_H

class Player {
public:
    void Update();
};

#endif

実行結果

このヘッダーファイルは、同じ翻訳単位内で複数回includeされても
class Player が一度だけ定義される

つまり、最初に読み込まれた時だけ中身を有効にして、2回目以降は読み飛ばすわけです。

なぜインクルードガードが必要?

インクルードガードが必要な理由は、ヘッダーファイル同士が連鎖して読み込まれることがかなり多いからです。

たとえば、main.cppPlayer.h を読み込み、さらにその中で Common.h を読み込み、別のヘッダーからもまた Common.h を読む、ということは普通にあります。そうすると、ガードがない場合は同じ定義が何度も展開されてしまいます。

ヘッダーファイルの基本や #include の書き分けがまだ曖昧な方は、#includeの””と<>の違いの記事もかなり相性がいいです。

インクルードガードの仕組み

流れとしてはかなりシンプルです。

  • #ifndef PLAYER_H で、まだ PLAYER_H が定義されていないか確認する
  • 定義されていなければ #define PLAYER_H でマクロを定義する
  • その下のヘッダ本体を有効にする
  • 次に同じヘッダーが読み込まれた時は、すでに PLAYER_H が定義済みなので中身を飛ばす

「このヘッダーはもう通りましたよ」という印を付けているイメージです。

#pragma onceとの違いは?

#pragma once も、同じヘッダーファイルの多重読み込みを防ぐための書き方です。

書き方はかなりシンプルで、ヘッダーファイルの先頭に1行書くだけです。

▼Player.h


#pragma once

class Player {
public:
    void Update();
};

実行結果

このヘッダーファイルも、同じ翻訳単位内で一度だけ読み込まれる

違いをざっくり言うと、次の通りです。

  • #ifndef:標準的なプリプロセッサの書き方。移植性が高い
  • #pragma once:書くのが楽で見やすい。今の主要なコンパイラではかなり広く使える

つまり、どちらも目的は同じですが、書き方と互換性の考え方が少し違うわけです。

どっちを使うべき?

結論としては、普段のC++開発なら #pragma once で困らない場面がかなり多いです。

実際、最近のC++記事やライブラリのサンプルでも #pragma once はよく見かけます。ただし、仕組みを理解するうえでは #ifndef の方が「何を防いでいるのか」が見えやすいです。

なので初心者のうちは、まずインクルードガードの意味を理解し、そのうえで実務では #pragma once を使うという流れがかなりわかりやすいと思います。

【重要】私が実際にインクルードガードで困った体験談

私も自主制作で複数ファイルに分け始めた頃、インクルードガードの意味をよく理解しないままヘッダーファイルを書いていて、かなりハマったことがあります。

クラスを分けて Player.hEnemy.h を作っていたのですが、共通で使う構造体を入れたヘッダーをいろいろな場所から読み込んでいたところ、突然「redefinition」系のエラーが大量に出るようになりました。最初はクラス名の付け方が悪いのかと思ってかなり悩みました。

原因は単純で、共通ヘッダーにガードを書いていなかっただけでした。そこで #ifndef を入れたところ、エラーがすぐ解消しました。

ただ、そのあと今度はヘッダー内でグローバル変数を定義してしまっていて、別の多重定義エラーにもハマりました。これで、インクルードガードは万能ではなく、「同じ.cpp内での多重include防止」が役割だと理解するのが大事だと実感しました。

こういう時は、エラーメッセージを追いながらヘッダーファイルの関係を整理するとかなり原因を見つけやすいです。エラーの追い方に不安がある方は、C++でのデバッグのやり方の記事も参考になります。

インクルードガード使用時のよくある失敗例と対処法

  • マクロ名が他のヘッダーと重複する
    COMMON_H のような名前はかぶりやすいです。ファイル名を含めた一意な名前にすると安全です。
  • #endif を書き忘れる
    プリプロセッサのエラーが出て、かなり分かりづらくなります。テンプレートとしてコピペする形でも大丈夫なので、3点セットで書きましょう。
  • ガードを書けば多重定義が全部防げると思ってしまう
    ヘッダーに変数定義や非inline関数定義を書くと、別の.cppから読まれてリンクエラーになることがあります。グローバル変数を共有したい時は externの記事 もあわせて読むと整理しやすいです。

実践例

では、インクルードガード付きのヘッダーファイルを分割して使う、かなり基本的な形を見てみましょう。

▼Player.h


#ifndef PLAYER_H
#define PLAYER_H

class Player {
public:
    void PrintHp() const;
};

#endif

▼Player.cpp


#include <iostream>
#include "Player.h"

void Player::PrintHp() const {
    std::cout << "HP: 100\n";
}

▼main.cpp


#include "Player.h"
#include "Player.h"

int main() {
    Player player;
    player.PrintHp();
    return 0;
}

実行結果

HP: 100

この例では、main.cpp でわざと "Player.h" を2回読み込んでいますが、インクルードガードがあるので問題なくコンパイルできます。

クラスをヘッダーとcppに分ける書き方そのものがまだ不安な場合は、クラスの記事もあわせて読むと理解しやすいです。

注意点

  • インクルードガードは同じ翻訳単位内での重複読み込み防止の仕組みです
  • #pragma once は便利ですが、仕組み理解のために #ifndef も知っておくとかなり役立ちます
  • ヘッダーには「宣言」を置き、「定義」をどこに書くかを意識するのが大切です

特にC++では、ヘッダーファイルまわりの理解が進むと、クラス分割や複数ファイル構成がかなりスムーズになります。

まとめ

  • インクルードガード(#ifndefは、同じヘッダーファイルの多重読み込みを防ぐ仕組み
  • #ifndef#define#endif の3つで書く
  • #pragma once も同じ目的で使われる、よりシンプルな書き方
  • インクルードガードは万能ではなく、ヘッダー内の定義の置き方には別途注意が必要
  • 初心者のうちは、まず仕組みを #ifndef で理解しておくのがおすすめ

インクルードガードは、最初はおまじないのように見えるかもしれませんが、ヘッダーファイルを安全に使うためのかなり重要な基本です。

「同じヘッダーを何回読んでも、定義は一度だけにする」という目的が分かると、C++の複数ファイル構成もかなり理解しやすくなります。

ここまで読んでくださり、ありがとうございました。

関連記事