閉じる

モダンC++ヘッダ(.hpp,#pragma once)入門:多重定義を一発で防ぐ

モダンC++では、ヘッダーファイルの設計がコードの保守性やビルド速度に直結します。

特に#pragma onceは重複インクルードによる多重定義をシンプルに防げるため、初心者の方にも強くおすすめできます。

本記事では.hpp拡張子の意味#pragma onceの正しい使い方を中心に、初学者がつまずきやすいポイントを実例で丁寧に解説します。

.hppとは?C++ヘッダーファイルの基本

ヘッダーファイルの役割と#includeの流れ

C++のヘッダーファイルは、関数やクラスの宣言を他ファイルに伝えるための入口です。

ソースファイル(.cpp)はコンパイル時に別々の翻訳単位として処理されるため、他の翻訳単位で定義されたシンボルを使うには、その型や関数の存在を事前に知っておく必要があります。

これを満たすのが#includeによるヘッダの取り込みです。

インクルード時には、プリプロセッサが#include "xxx.hpp"の行を、指定ファイルの中身に置き換えます。

つまり「テキストの貼り付け」が起きるため、同じ宣言が何度も貼り付くと多重定義(再定義)のコンパイルエラーにつながります。

そこで必要になるのが多重インクルードの防止策です。

.hと.hppの違い(モダンC++視点)

歴史的には、C言語と共有する.hが使われてきました。

モダンC++では、C++専用のヘッダであることを明示するために.hppを使う慣習が広まりました。

特にプロジェクトが大きくなると、CのヘッダとC++のヘッダを区別できることは読みやすさと理解しやすさにつながります。

以下は役割の目安です。

  • .h: C由来やCと共有されるインターフェース、または歴史的経緯で使用
  • .hpp: C++専用のヘッダ(名前空間、クラス、テンプレートなどC++機能を使う宣言)

モダンC++の新規コードでは、C++専用ヘッダに.hppを使うと意図が明快です。

拡張子の選び方(.hppを推奨)

初学者が新規にC++ヘッダを作るなら.hppを推奨します。

次の点で有利だからです。

  • Cとの区別が明確、IDEでのファイル検索・テンプレートとの相性がよい、チーム内での役割分担が伝わりやすい、などです。
  • 既存プロジェクトの規約がある場合はそれに合わせつつ、新規C++コードは.hppという原則で揃えると混乱が減ります。

#pragma onceで多重定義を一発で防ぐ

#pragma onceの書き方と置く場所

#pragma onceそのヘッダファイルを1翻訳単位につき一度だけ有効にすることをコンパイラに指示します。

書く場所はヘッダの先頭(最初の非コメント行)が定石です。

コメントやライセンス文の直後、最初に置きましょう。

最小サンプル(.hppとmain.cpp)

以下は#pragma onceを使った最小サンプルです。

ヘッダをわざと2回インクルードしていますが、再定義は発生しません。

C++
// greeter.hpp
#pragma once

#include <string>   // std::string を使うため

namespace demo {

// C++のクラス宣言とインライン定義(ヘッダに置けます)
class Greeter {
public:
    explicit Greeter(std::string name) : name_(std::move(name)) {}

    // メッセージ文字列を返す(インライン)
    std::string message() const {
        return "Hello, " + name_ + "!";
    }

private:
    std::string name_;
};

// ヘッダ内に定数を定義する場合は inline を付ける(C++17〜)
inline constexpr int version = 1;

} // namespace demo
C++
// main.cpp
#include "greeter.hpp"
#include "greeter.hpp" // わざと2回。#pragma once があれば安全

#include <iostream>

int main() {
    demo::Greeter g("Taro");
    std::cout << g.message() << " v" << demo::version << "\n";
    return 0;
}
実行結果
Hello, Taro! v1

この例では、クラスのメンバ関数をヘッダ内で定義しています。

インライン定義はヘッダに置けますが、一般的な非テンプレートの関数実装は.cppに分離するのが基本です(後述)。

よくあるミス(書き忘れや重複インクルード)

#pragma onceを書き忘れると、同じ翻訳単位で同じヘッダが2回取り込まれたとき、クラスや関数の再定義エラーになります。

再現例を示します。

C++
// greeter_bad.hpp
// #pragma once を意図的に書き忘れた例
class Foo {};
C++
// main_bad.cpp
#include "greeter_bad.hpp"
#include "greeter_bad.hpp" // 2回目(誤り)

コンパイルエラー例:

text
error: redefinition of 'class Foo'
note: previous definition of 'class Foo' was here

また、別々のヘッダが互いに同じヘッダを#includeしているような間接的な重複インクルードも頻出します。

とにかく各.hppの先頭に#pragma onceを入れる習慣を付けましょう。

インクルードガードとの比較(include guard)

従来の書き方(#ifndef〜#endif)

歴史的には以下のようなinclude guardが使われてきました。

動作は#pragma onceと同じ目的です。

C++
// greeter.hpp (include guard 版)
#ifndef DEMO_GREETER_HPP_
#define DEMO_GREETER_HPP_

#include <string>

namespace demo {

class Greeter {
public:
    explicit Greeter(std::string name);
    std::string message() const;

private:
    std::string name_;
};

} // namespace demo

#endif // DEMO_GREETER_HPP_

上では実装を省略していますが、ヘッダは宣言に留め、実装は.cppへ分けるのが基本方針です。

#pragma onceの利点と注意点

#pragma onceの主な利点は、以下の通りです。

  • 記述が短く、マクロ名の一意化を考えなくてよい
  • タイプミスやコピペミスが減る(ガード名がファイル名とズレる事故がない)
  • 多くの実装でビルド時間上の最適化が期待できることがある

注意点としては、形式上はC++標準の機能ではなく処理系拡張である点です。

ただし、主要なコンパイラ(gcc/Clang/MSVC)は長年サポートしており、実務でも事実上の標準として扱われています。

ごくまれに、同一ファイルの重複コピーやシンボリックリンクで奇妙な配置をした場合に挙動が問題になる報告がありますが、通常のプロジェクト構成ではまず問題になりません。

以下に簡単な比較を示します。

項目#pragma onceinclude guard(#ifndef〜)
記述の簡単さとても簡単(1行)マクロ名決めと3行が必要
ミスしにくさ高いマクロ名の重複・タイポが起こりやすい
標準性非標準だが事実上の標準標準的プリプロセッサ機能のみ
ビルド最適化実装が最適化することあり実装依存だが一般に同等
例外的な環境ほぼ問題なしほぼ問題なし

どちらを選ぶかの結論(初心者向け)

初心者には#pragma onceを強く推奨します。

短く、間違えにくく、主要コンパイラで広く動作するからです。

既存コードがインクルードガードで統一されていれば、それに合わせても問題ありませんが、新規は#pragma onceで揃えるのが無難です。

モダンなヘッダ(.hpp)の書き方チェックリスト

ファイル名と拡張子の決め方(.hpp)

ヘッダはC++専用であれば.hppにします。

ファイル名はプロジェクト内で一意になるようにし、project/module_name.hppのようにディレクトリ構成で整理すると衝突を避けやすくなります。

マクロやガード名が不要になる分、ファイル名の表記揺れだけに注意を払えば十分です。

先頭に#pragma onceを入れる

.hpp最初の非コメント行に#pragma onceを置きます。

ライセンスブロックやファイル概要コメントの直後が定位置です。

これにより、直接でも間接でも同じヘッダが複数回取り込まれても安全です。

宣言だけを書く(実装は別ファイル)

ヘッダには宣言(インターフェース)を中心に置き、実装は.cppに分けるのが基本です。

こうすることでビルド時間短縮、依存の減少、変更波及の抑制が期待できます。

  • 例外として、テンプレートインライン関数constexprは定義をヘッダに置く必要があります。C++17以降なら、ヘッダ内のグローバル定数にinlineを付けるとODR違反を避けられます(inline constexprなど)。

必要最小限の#includeにする

ヘッダに書く#include宣言に必須な最小限に抑えます。

ポインタや参照だけが必要なら前方宣言(forward declaration)で済ませ、本体定義や使用が必要になる.cpp側で実ヘッダを#includeします。

これにより依存の連鎖と再コンパイルが減り、ビルドが速くなります。

<>と””の使い分けと順序をそろえる

#includeの書き分けは次の通りです。

  • #include <...>: 標準ライブラリやシステムのヘッダ
  • #include "...": プロジェクト内のヘッダ

読みやすさのため、順序を統一しましょう。

一般的には「標準ライブラリ → 外部ライブラリ → 自プロジェクト」の順に並べます。

例:

C++
// sample.hpp
#pragma once

// 1) 標準ライブラリ
#include <string>
#include <vector>

// 2) 外部ライブラリ(例)
// #include <fmt/format.h>

// 3) 自プロジェクト
#include "project/config.hpp"

// 以降: 宣言のみを記述
namespace project {
class Widget;
class Manager; // 前方宣言の例(ここでは定義は不要)
} // namespace project

この順序を守ると、隠れた依存や循環参照に早く気づきやすくなります

まとめ

本記事では、モダンC++のヘッダ作法として.hpp拡張子の採用#pragma onceの徹底を解説しました。

#pragma onceは多重インクルードを簡潔に防ぎ、初学者の典型的なエラー(再定義)を未然に防ぎます。

ヘッダは宣言中心、実装は.cppへ、必要最小限の#includeという原則を守れば、ビルド時間短縮と保守性の向上が期待できます。

将来的にC++20のモジュールが普及しても、当面の現場では.hpp + #pragma onceが主流であり続けるでしょう。

まずは各ヘッダの先頭に#pragma onceを置くことから、今日のプロジェクトに取り入れてみてください。

C++ 実践TIPS - ビルド、デバッグ、その他
  • モダンなヘッダーファイル (.hpp, #pragma once)(1/1)
この記事を書いた人
エーテリア編集部
エーテリア編集部

C++をこれから学ぶ方に向けて、基礎的な文法や標準ライブラリの使い方を紹介します。モダンな書き方も初心者に合わせてやさしく説明しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!