閉じる

【C++】ヘッダファイルの書き方入門|役割から実践例まで徹底解説

C++でプログラムを分割して書くときに欠かせないのがヘッダファイルです。

しかし、いざ自分で書こうとすると「どこまで書けばいいのか」「includeガードはどうするのか」など、細かい疑問がたくさん出てきます。

本記事ではC++のヘッダファイルの基本的な役割から、実際の書き方、よくあるミスの防ぎ方までを、図解とサンプルコードを交えて丁寧に解説します。

ヘッダファイルとは何か

ヘッダファイルの基本的な役割

C++におけるヘッダファイル(拡張子.h.hpp)は、宣言(interface)をまとめて他のファイルに共有するためのファイルです。

通常、1つのクラスやライブラリについて

  • ヘッダファイル: クラスや関数の宣言を書く
  • ソースファイル(.cpp): 実際の実装(定義)を書く

という役割分担をします。

文章で表すと、次のようなイメージです。

  • ヘッダファイル: 「このクラスはこういうメンバ関数を持っています」と外部に約束する名刺・仕様書
  • ソースファイル: 「そのメンバ関数はこういう処理を行います」と中身を実装した本体

他のファイルから使いたい関数やクラスは、原則としてヘッダファイルで宣言し、ソースファイルで定義する、という流れを覚えておくと理解しやすくなります。

なぜヘッダファイルが必要なのか

ヘッダファイルが必要になる理由は、コンパイラがコンパイル単位ごと(通常1つのcppファイルごと)に処理を行うからです。

コンパイラは1つの.cppファイルを読むとき、その中で使われている

  • 関数のシグネチャ(戻り値の型、引数の型)
  • クラスや構造体のメンバ構成
  • 定数の型や値(一部)

といった情報をすべて知っている必要があります。

しかし、他の.cppファイルの中身を勝手に覗くことはしません。

そこで、必要な情報だけを集約したヘッダファイルを#includeして共有するという仕組みが使われます。

ヘッダファイルの基本構造

最小構成のサンプル

まずは、C++のヘッダファイルの典型的な形を見てみます。

C++
// math_utils.h
#ifndef MATH_UTILS_H   // includeガード(後述)
#define MATH_UTILS_H

// 関数の宣言
int add(int a, int b);       // 足し算
int multiply(int a, int b);  // 掛け算

#endif // MATH_UTILS_H

上の例は最小限の構成ですが、ヘッダファイルの基本要素がすべて含まれています。

  • includeガード
  • 関数の宣言

ヘッダファイルには「実装」ではなく「宣言」を書く、という点を意識すると整理しやすくなります。

includeガードと#pragma once

includeガードの役割と書き方

ヘッダファイルを書くときに必ず書くべきものincludeガードです。

これは、同じヘッダファイルが複数回#includeされても、内容が1回だけ有効になるようにする仕組みです。

典型的な書き方は次の通りです。

C++
#ifndef MATH_UTILS_H
#define MATH_UTILS_H

// ここにヘッダの本体を書く

#endif // MATH_UTILS_H

意味としては

  • #ifndef MATH_UTILS_H: まだMATH_UTILS_Hが定義されていなければ
  • #define MATH_UTILS_H: ここで定義済みにしておく
  • #endif: ここまでが対象範囲

という形で、2回目以降にこのヘッダが読み込まれた場合には、中身がスキップされます。

マクロ名はプロジェクト内で一意になるように、ファイル名や名前空間を組み合わせるのがおすすめです。

C++
#ifndef PROJECT_MATH_UTILS_H
#define PROJECT_MATH_UTILS_H
// ...
#endif // PROJECT_MATH_UTILS_H

#pragma once との違い

includeガードの代わりに#pragma onceを使う書き方も広く使われています。

C++
// math_utils.h
#pragma once

int add(int a, int b);
int multiply(int a, int b);

#pragma onceは「このファイルは1回だけインクルードすればよい」という意味で、実質的にincludeガードと同じ効果を持ちます。

利点としては、ヘッダの先頭に1行書くだけで済むため

  • マクロ名を考える必要がない
  • タイプミスのリスクが減る
  • コードがすっきりする

といった点があります。

ただし、厳密には標準規格で保証されている機能ではなく処理系依存の拡張です。

とはいえ、現在の主要なコンパイラ(GCC、Clang、MSVCなど)ではほぼサポートされているため、実務でも広く使われています。

方針としては

  • 学習用・個人プロジェクト: #pragma onceでも問題ないケースが多い
  • 移植性を厳密に求める場合: includeガードを使う

というように、プロジェクトのポリシーに合わせて使い分けるのがよいです。

関数宣言用のヘッダを書く

関数宣言だけをまとめる例

簡単な数学ユーティリティを例に、ヘッダとソースを分けて書いてみます。

C++
// math_utils.h
#pragma once  // または includeガード

// 足し算の関数宣言
int add(int a, int b);

// 引き算の関数宣言
int subtract(int a, int b);
C++
// math_utils.cpp
#include "math_utils.h"  // 自分のヘッダを必ず先頭で include する

// 足し算の定義
int add(int a, int b) {
    return a + b;
}

// 引き算の定義
int subtract(int a, int b) {
    return a - b;
}
C++
// main.cpp
#include <iostream>
#include "math_utils.h"  // 宣言だけ分かれば呼び出せる

int main() {
    int x = 10;
    int y = 4;

    std::cout << "add: " << add(x, y) << std::endl;
    std::cout << "subtract: " << subtract(x, y) << std::endl;

    return 0;
}

このように、使う側(main.cpp)はヘッダファイルだけを知っていればよく、実装(math_utils.cpp)の中身を意識する必要はありません

上記の3ファイルをコンパイルして実行すると、次のような出力になります。

実行結果
add: 14
subtract: 6

クラスを宣言するヘッダの書き方

シンプルなクラスのヘッダ

クラスをヘッダファイルに書く基本形を見てみます。

C++
// rectangle.h
#pragma once

// 長方形を表すクラス
class Rectangle {
public:
    // コンストラクタの宣言
    Rectangle(double w, double h);

    // 面積を計算するメンバ関数の宣言
    double area() const;

    // 周の長さを計算するメンバ関数の宣言
    double perimeter() const;

private:
    // 幅と高さのメンバ変数
    double width_;
    double height_;
};
C++
// rectangle.cpp
#include "rectangle.h"

// コンストラクタの定義
Rectangle::Rectangle(double w, double h)
    : width_(w), height_(h) {}

// 面積の定義
double Rectangle::area() const {
    return width_ * height_;
}

// 周の長さの定義
double Rectangle::perimeter() const {
    return 2 * (width_ + height_);
}
C++
// main.cpp
#include <iostream>
#include "rectangle.h"

int main() {
    Rectangle rect(3.0, 4.0);

    std::cout << "area: " << rect.area() << std::endl;
    std::cout << "perimeter: " << rect.perimeter() << std::endl;

    return 0;
}

実行すると、次のような出力が得られます。

実行結果
area: 12
perimeter: 14

このように、クラスの宣言はヘッダファイルに、メンバ関数の中身はソースファイルに分けるのが基本です。

なぜクラスの実装をヘッダに書きすぎてはいけないのか

クラスのメンバ関数をヘッダ内で定義してしまうことも可能です。

C++
// rectangle_inline.h
#pragma once

class Rectangle {
public:
    Rectangle(double w, double h)
        : width_(w), height_(h) {}

    double area() const {
        return width_ * height_;
    }

    double perimeter() const {
        return 2 * (width_ + height_);
    }

private:
    double width_;
    double height_;
};

このようにすると、1ファイルで完結し便利に見えますが、大きなクラスや頻繁に変更される実装をヘッダに書くと

  • そのヘッダをincludeしているすべてのソースが再コンパイルされる
  • コンパイル時間が増える
  • 実装の差し替えがしにくい

といった問題につながります。

小さなテンプレートクラスやインライン化したいごく小さい処理以外は、できるだけソースファイルに定義するのがおすすめです。

ヘッダファイルで書いてよいもの・いけないもの

書いてよいもの(典型例)

ヘッダファイルに書くのは、基本的に「宣言」「インターフェース」に関する情報です。

代表的なものを表に整理します。

種類具体例備考
関数宣言int add(int a, int b);本体はcppへ
クラス・構造体の宣言class Foo { ... };メンバの定義を含む
enumの宣言enum Color { Red, Green };列挙値を共有
定数の宣言extern const int kMaxSize;定義はcppへ
インライン関数の定義inline int sq(int x) { ... }小さい処理
テンプレートの定義template<typename T> ...通常ヘッダ内定義

テンプレートは定義もヘッダに書く必要がある点が、通常の関数やクラスと大きく異なるポイントです。

書かないほうがよいもの

逆に、ヘッダファイルに安易に書くと問題になりやすいものもあります。

  • 非テンプレート関数の本体(大きな処理)
  • 非テンプレートクラスのメンバ関数の本体(インライン化したい小さなものを除く)
  • 変数の定義(意図しない重複定義の原因)
  • using namespace std; などの広いusing宣言

特にグローバル変数の定義は注意が必要です。

C++
// bad.h
#pragma once

int g_value = 0;  // これは「定義」なのでNG

このヘッダを複数のcppからincludeすると、同じ名前の変数が複数の翻訳単位に定義されてしまい、リンカエラーになります。

グローバル変数が必要な場合は、次のように

  • ヘッダにはextern付きで「宣言」だけ
  • cppに1つだけ「定義」

を書くようにします。

C++
// config.h
#pragma once

extern int g_value;  // 宣言だけ
C++
// config.cpp
#include "config.h"

int g_value = 0;  // 定義はここだけ

ヘッダファイルのincludeのコツ

標準ヘッダと自作ヘッダの順番

ソースファイル(.cpp)では、まず最初に対応する自分自身のヘッダをincludeするのがおすすめです。

C++
// rectangle.cpp
#include "rectangle.h"   // 1. 自分のヘッダ
#include <iostream>      // 2. 標準ライブラリ
#include "config.h"      // 3. 他のプロジェクトヘッダ

// ...

この順番にすることで

  • rectangle.h がそれ自身でコンパイル可能(他に依存していない)かどうかを検証しやすい
  • ヘッダの中で不足しているincludeに気付きやすい

といったメリットがあります。

ヘッダの中で必要なヘッダは自分でincludeする

ヘッダファイルを書くときに大事なのは、そのヘッダ単体をincludeしただけでコンパイルに必要な情報が揃っているようにすることです。

例えば、次のようなクラスを考えます。

C++
// bad_rectangle.h
#pragma once

class BadRectangle {
public:
    // std::string を引数に使っているが…
    void setName(const std::string& name);

private:
    std::string name_;
};

このヘッダはstd::stringを使っていますが、<string>をincludeしていないため、これを単独でincludeするとコンパイルエラーになります。

正しくは次のように#include <string>を書く必要があります。

C++
// good_rectangle.h
#pragma once

#include <string>  // 自分が使う型は自分で include する

class GoodRectangle {
public:
    void setName(const std::string& name);

private:
    std::string name_;
};

「使う側が勝手に<string>をincludeしてくれるだろう」と期待しないことが、保守しやすいヘッダを書くためのコツです。

実践ミニプロジェクト: シンプルなライブラリを作る

構成の全体像

ここまでの内容を踏まえて、簡単な「数値計算ライブラリ」を作る例で、ヘッダファイルの書き方をまとめてみます。

ディレクトリ構成は次のようにします。

ディレクトリファイル名役割
include/math_utils.h基本数学関数の宣言
include/stats.h統計関数の宣言
src/math_utils.cppmath_utils の定義
src/stats.cppstats の定義
src/main.cpp動作確認用の実行ファイル

math_utils.h と math_utils.cpp

C++
// include/math_utils.h
#pragma once

// 足し算
int add(int a, int b);

// 平方
int square(int x);
C++
// src/math_utils.cpp
#include "math_utils.h"

int add(int a, int b) {
    return a + b;
}

int square(int x) {
    return x * x;
}

stats.h と stats.cpp

C++
// include/stats.h
#pragma once

// 平均値を計算する関数
double mean(const int* values, int size);

// 分散を計算する関数
double variance(const int* values, int size);
C++
// src/stats.cpp
#include "stats.h"
#include "math_utils.h"  // add や square を利用する

double mean(const int* values, int size) {
    // 合計値を計算
    int sum = 0;
    for (int i = 0; i < size; ++i) {
        sum = add(sum, values[i]);  // math_utils の関数を使用
    }

    return static_cast<double>(sum) / size;
}

double variance(const int* values, int size) {
    double m = mean(values, size);

    double sum_sq = 0.0;
    for (int i = 0; i < size; ++i) {
        double diff = values[i] - m;
        sum_sq += diff * diff;
    }

    return sum_sq / size;
}

main.cpp からの利用

C++
// src/main.cpp
#include <iostream>
#include "math_utils.h"
#include "stats.h"

int main() {
    int data[] = {1, 2, 3, 4, 5};
    int size = sizeof(data) / sizeof(data[0]);

    std::cout << "add(2, 3) = " << add(2, 3) << std::endl;
    std::cout << "square(4) = " << square(4) << std::endl;

    std::cout << "mean = " << mean(data, size) << std::endl;
    std::cout << "variance = " << variance(data, size) << std::endl;

    return 0;
}

想定される実行結果は、次のようになります。

実行結果
add(2, 3) = 5
square(4) = 16
mean = 3
variance = 2

この小さな例の中だけでも、ヘッダファイルに関する大切なポイントがすべて入っています。

  • 宣言はヘッダ、定義はcpp
  • 必要な宣言をヘッダ経由で共有
  • cpp側では自分のヘッダを先頭でinclude
  • 他のモジュールの機能は、そのヘッダをincludeして利用

まとめ

ヘッダファイルは、C++におけるモジュール間の約束事を記述する大切なファイルです。

includeガードや#pragma onceでの二重include対策、宣言と定義の適切な分離、自分が使う型や関数を自分でincludeする習慣を身につけることで、保守性の高いコードを書けるようになります。

まずは小さな関数やクラスを、ヘッダとソースに分けて実装するところから練習してみてください。

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

URLをコピーしました!