モダン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回インクルードしていますが、再定義は発生しません。
// 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
// 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回取り込まれたとき、クラスや関数の再定義エラーになります。
再現例を示します。
// greeter_bad.hpp
// #pragma once を意図的に書き忘れた例
class Foo {};
// main_bad.cpp
#include "greeter_bad.hpp"
#include "greeter_bad.hpp" // 2回目(誤り)
コンパイルエラー例:
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
と同じ目的です。
// 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 once | include 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 "..."
: プロジェクト内のヘッダ
読みやすさのため、順序を統一しましょう。
一般的には「標準ライブラリ → 外部ライブラリ → 自プロジェクト」の順に並べます。
例:
// 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
を置くことから、今日のプロジェクトに取り入れてみてください。