HOWTO · C++

C++ の #pragma once:インクルードガードと移植性

C++ のヘッダー重複インクルードを #pragma once またはインクルードガードで防ぐ方法を説明します。

このページの内容

#pragma once は、同じ翻訳単位でヘッダーファイルを最大一度だけ処理するよう実装に指示するプリプロセッサディレクティブです。プロジェクトが対応するすべてのコンパイラーで実装されているなら、通常の C/C++ ヘッダーの先頭に置きます。標準的な移植性、未知のコンパイラー、または既存の規約が必要なら、一意の #ifndef/#define インクルードガードを使用します。どちらもプログラム全体で一度だけ使えるようにするものではありません。

宣言の前に #pragma once を置く

// config.hpp
#pragma once

class Config {
public:
    int port() const { return 8080; }
};

セミコロンは不要です。プリプロセッサはコンパイル前に #include を処理するため、同じ翻訳単位を作る途中でヘッダーテキストをもう一度処理しません。GCC、Clang、MSVC では広く実装されていますが、ISO C/C++ の標準ディレクティブではありません。GCC の once-only header の説明を参照し、実際にサポートを約束するツールチェーンで判断してください。

直接・間接インクルードを確認する

この検証済みの 3 ファイルでは、config.hpp を server.hpp 経由と main.cpp から直接の両方で読み込みます。

// server.hpp
#pragma once
#include "config.hpp"

class Server { Config config_; };
// main.cpp
#include "server.hpp"
#include "config.hpp"

int main() {
    return Config{}.port() == 8080 ? 0 : 1;
}

3 ファイルを同じディレクトリに保存して実行します。

g++ -std=c++17 -Wall -Wextra main.cpp -o app
./app

config.hpp に #pragma once があれば出力はなく、./app は状態 0 で終了します。g++ (Ubuntu 15.2.0-16ubuntu1) 15.2.0 で確認済みです。config.hpp からこの行だけを削除すると、GCC は状態 1 で終了し、直接と先行する間接インクルードによる Config の再定義を報告します。これは翻訳単位ごとの動作であり、MSVC/Clang で個別に実行した例ではありません。

標準のインクルードガードを使う

// config.hpp
#ifndef EXAMPLE_CONFIG_HPP
#define EXAMPLE_CONFIG_HPP

class Config {
public:
    int port() const { return 8080; }
};

#endif  // EXAMPLE_CONFIG_HPP

最初のインクルードでマクロを定義し、以後は囲まれたテキストをスキップします。プロジェクト名、ディレクトリ名、ファイル名から説明的で一意の名前を作ります。CONFIG_H は別のヘッダーと衝突し得ます。__ を含む名前、または _ の次が大文字の名前は実装用に予約されています。

#include はテキストとしての取り込みです。C++ の宣言を調べる前に、プリプロセッサが指示行をヘッダーテキストで置き換えます。この例では config.hpp がまず server.hpp 経由で、その後 main.cpp から直接入るため、保護がなければ Config 定義が二つになります。したがって保護は、たまたま読み込むソースだけでなくヘッダー自身に置きます。これはコンパイルエラーであり、別々にコンパイルしたファイルのリンカーエラーとは異なります。

使い分ける

状況 選択 理由
対象コンパイラーがすべて #pragma once を実装する #pragma once 短く、ガードマクロ衝突がない。
公開ライブラリ、未知のコンパイラー、厳密な標準移植性 インクルードガード 標準プリプロセッサディレクティブ。
リポジトリの規約がある 既存規約 ヘッダーの保守性を保てる。
X-macro リストのように意図して複数回読むヘッダー 原則どちらも使わない 繰り返しが目的である。

通常のヘッダーに両方を一律で追加する必要はありません。一方で十分であり、一般的なガードはコンパイラーにも認識されます。速度向上も常にあるとは言えないため、変更理由がビルド時間なら測定します。#pragma once は実装によるファイル同一性の判定に依存するので、別名、生成ファイル、ネットワークファイルシステム、特殊なパスは境界条件になります。ガードはこの問題を避けますが、マクロの一意性が必要です。

ヘッダー保護で解決しないこと

保護は翻訳単位ごとに働き、複数定義のリンカーエラー全般や One Definition Rule (ODR) を解決しません。非 inline の自由関数をヘッダーに定義すると、各 .cpp が外部定義を作ることがあります。通常の定義は .cpp に置くか、設計上必要な場合だけ inline またはテンプレートを使います。両方の型が完全型を必要とする循環依存も解決しないため、ポインターや参照には前方宣言を使うか実装を移します。C++20 モジュールも別物で、import はヘッダーテキストを取り込みません。

まとめ

拡張のサポートを受け入れられるなら #pragma once、標準移植性には一意のインクルードガードを使います。直接・間接の経路を確認し、ODR、循環、意図的な複数インクルード、モジュールは別に診断します。