C++のヘッダーファイルについて

AI実装検定のご案内

C++のヘッダーファイルとは、主に関数・クラス・構造体・定数・型・テンプレートなどの宣言を書くためのファイルです。

拡張子には、一般的に次のようなものが使われます。

.h
.hpp
.hh
.hxx

C++では、プログラムを複数のファイルに分けて開発することが多くあります。

そのとき、別のファイルで定義された関数やクラスを使うために、「この関数があります」「このクラスがあります」と知らせる役割を持つのがヘッダーファイルです。

簡単に言うと、ヘッダーファイルは他のソースファイルに公開するインターフェースを書く場所です。

目次

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

ヘッダーファイルの主な役割は、プログラム内で共通して使う宣言を複数のファイルから利用できるようにすることです。

たとえば、次のようなファイル構成があるとします。

main.cpp
math_utils.cpp
math_utils.hpp

math_utils.hpp には、関数の宣言を書きます。

// math_utils.hpp
#pragma once

int add(int a, int b);

math_utils.cpp には、関数の実装を書きます。

// math_utils.cpp
#include "math_utils.hpp"

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

main.cpp では、ヘッダーファイルを読み込むことで add 関数を使えるようになります。

// main.cpp
#include <iostream>
#include "math_utils.hpp"

int main() {
    std::cout << add(3, 5) << std::endl;
}

このように、ヘッダーファイルには「どのような関数やクラスが使えるのか」を書き、実際の処理内容は .cpp ファイルに書くのが基本です。

ヘッダーは「使い方」を示すファイル

ヘッダーファイルは、関数やクラスの使い方を他のファイルに知らせるためのものです。

たとえば、次の宣言を見るだけで、

int add(int a, int b);

add という関数があり、int 型の引数を2つ受け取り、int 型の値を返すことが分かります。

ただし、この時点では「どのように足し算しているか」という実装の中身までは分かりません。

実装の中身は .cpp ファイルに書かれます。

ヘッダーは複数のファイルから共有される

同じ関数やクラスを複数の .cpp ファイルから使いたい場合、それぞれのファイルに同じ宣言を書くのは非効率です。

ヘッダーファイルに宣言をまとめておけば、必要なファイルで #include するだけで同じ宣言を共有できます。

これにより、コードの重複を減らし、変更にも強い構成にできます。

ヘッダーファイルとソースファイルの違い

C++では、一般的にヘッダーファイルとソースファイルを分けて書きます。

種類拡張子の例主な内容
ヘッダーファイル.h, .hpp, .hh, .hxx宣言、クラス定義、型定義、テンプレートなど
ソースファイル.cpp, .cc, .cxx関数やメンバー関数の実装

基本的な考え方は、次のとおりです。

ヘッダーファイル:何が使えるかを書く
ソースファイル:どのように動くかを書く

たとえば、User クラスを作る場合、ヘッダーにはクラスの宣言を書きます。

// User.hpp
#pragma once

#include <string>

class User {
public:
    User(const std::string& name);
    std::string getName() const;

private:
    std::string name_;
};

そして、ソースファイルには実装を書きます。

// User.cpp
#include "User.hpp"

User::User(const std::string& name)
    : name_(name) {}

std::string User::getName() const {
    return name_;
}

このように分けることで、コードの見通しが良くなり、機能ごとの管理もしやすくなります。

.h.hpp の違い

C++のヘッダーファイルには、.h.hpp などの拡張子が使われます。

ただし、C++の言語仕様として「C++では必ず .hpp を使う」と決まっているわけではありません。

拡張子はプロジェクトやチームの方針によって異なります。

一般的には、次のように使い分けられることが多いです。

拡張子よくある用途
.hC言語、またはC/C++共用のヘッダー
.hppC++専用のヘッダー
.hhC++プロジェクトで使われることがある
.hxxC++プロジェクトで使われることがある

実務では統一されていることが重要

.h.hpp のどちらを使うべきかは、プロジェクトによって異なります。

C++専用のコードでは .hpp を使うことも多いですが、C++でも .h を使うプロジェクトは多くあります。

重要なのは、拡張子そのものよりも、プロジェクト内でルールを統一することです。

#include の仕組み

ヘッダーファイルは、#include を使って読み込みます。

#include <iostream>
#include "User.hpp"

#include は、コンパイル前のプリプロセス段階で、指定したファイルの内容をその場所に展開する仕組みです。

つまり、

#include "User.hpp"

と書くと、コンパイラに渡される前に User.hpp の内容がその場所に挿入されるようなイメージです。

<...>"..." の違い

#include には、主に2つの書き方があります。

#include <iostream>
#include "User.hpp"

一般的には、次のように使い分けます。

書き方主な用途
#include <iostream>標準ライブラリや外部ライブラリのヘッダー
#include "User.hpp"自分のプロジェクト内のヘッダー

たとえば、標準ライブラリを使う場合は次のように書きます。

#include <iostream>
#include <string>
#include <vector>
#include <memory>

一方、自作のヘッダーを読み込む場合は次のように書きます。

#include "User.hpp"
#include "Database.hpp"
#include "Logger.hpp"

厳密には、<...>"..." ではヘッダーを探す順序が異なります。

一般的には、自作ヘッダーには "..."、標準ライブラリや外部ライブラリには <...> を使うと覚えておくとよいです。

ヘッダーファイルに書く主な内容

ヘッダーファイルには、他のファイルから利用する必要がある宣言を書きます。

代表的なものは次のとおりです。

関数の宣言

関数を他の .cpp ファイルから使いたい場合、ヘッダーファイルに関数宣言を書きます。

// calculator.hpp
#pragma once

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

実装は .cpp ファイルに書きます。

// calculator.cpp
#include "calculator.hpp"

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

int subtract(int a, int b) {
    return a - b;
}

クラスの宣言

クラスを複数のファイルで使いたい場合も、ヘッダーファイルにクラス定義を書きます。

// Person.hpp
#pragma once

#include <string>

class Person {
public:
    Person(const std::string& name, int age);

    void sayHello() const;
    int getAge() const;

private:
    std::string name_;
    int age_;
};

実装は .cpp ファイルに分けられます。

// Person.cpp
#include "Person.hpp"
#include <iostream>

Person::Person(const std::string& name, int age)
    : name_(name), age_(age) {}

void Person::sayHello() const {
    std::cout << "Hello, I'm " << name_ << std::endl;
}

int Person::getAge() const {
    return age_;
}

構造体の定義

構造体もヘッダーファイルによく書かれます。

// Point.hpp
#pragma once

struct Point {
    double x;
    double y;
};

このような単純なデータ構造は、ヘッダーにそのまま定義することが多いです。

enum / enum class

状態や種類を表す列挙型も、ヘッダーファイルに書くことが多いです。

// Status.hpp
#pragma once

enum class Status {
    Success,
    Error,
    Pending
};

C++では、通常の enum よりも enum class の方が名前の衝突を避けやすく、安全に扱いやすいです。

定数

定数もヘッダーに書くことがあります。

C++17以降では、ヘッダーに定数を書く場合、inline constexpr を使うと扱いやすくなります。

// constants.hpp
#pragma once

inline constexpr double Pi = 3.141592653589793;
inline constexpr int MaxUserCount = 100;

inline constexpr を使うと、複数の .cpp ファイルから同じヘッダーを読み込んでも、重複定義の問題を避けやすくなります。

テンプレート

テンプレートは、基本的にヘッダーファイルに実装まで書きます。

// max_value.hpp
#pragma once

template <typename T>
T maxValue(T a, T b) {
    return a > b ? a : b;
}

テンプレートは、使われた型に応じてコンパイル時にコードが生成されます。

そのため、テンプレートを使う側の .cpp ファイルから実装が見えている必要があります。

この理由から、テンプレート関数やテンプレートクラスはヘッダーに定義まで書くのが一般的です。

宣言と定義の違い

ヘッダーファイルを理解するうえで、宣言定義の違いは非常に重要です。

宣言とは

宣言とは、「このような関数や変数があります」とコンパイラに知らせるものです。

int add(int a, int b);

これは関数の宣言です。

関数名、引数、戻り値の型は分かりますが、処理の中身は書かれていません。

定義とは

定義とは、実際に中身や実体を作るものです。

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

これは関数の定義です。

このコードによって、add 関数の実際の処理内容が決まります。

変数の宣言と定義

変数の場合も、宣言と定義を区別する必要があります。

extern int count;

これは変数の宣言です。

一方、次のコードは変数の定義です。

int count = 0;

ヘッダーファイルに通常のグローバル変数定義を書くと、複数の .cpp ファイルから読み込まれたときに重複定義エラーになることがあります。

悪い例は次のとおりです。

// bad.hpp
#pragma once

int count = 0;

このヘッダーを複数の .cpp ファイルで読み込むと、それぞれの翻訳単位に count の定義が作られてしまいます。

より安全な書き方は、ヘッダーに宣言を書き、ソースファイルに定義を書く方法です。

// good.hpp
#pragma once

extern int count;
// good.cpp
#include "good.hpp"

int count = 0;

C++17以降であれば、inline 変数を使って次のように書くこともできます。

// config.hpp
#pragma once

inline int count = 0;

ただし、状態を持つグローバル変数はプログラムの見通しを悪くしやすいため、必要性をよく考えて使うべきです。

インクルードガードとは

ヘッダーファイルでは、同じファイルが複数回読み込まれることがあります。

たとえば、次のような構成を考えます。

// A.hpp
#include "Common.hpp"
// B.hpp
#include "Common.hpp"
// main.cpp
#include "A.hpp"
#include "B.hpp"

この場合、main.cpp から見ると Common.hpp が複数回読み込まれる可能性があります。

もし Common.hpp にクラス定義などがそのまま書かれていると、再定義エラーの原因になります。

そこで使うのが、インクルードガードです。

#ifndef USER_HPP
#define USER_HPP

class User {
public:
    void login();
};

#endif

このように書くことで、同じヘッダーが複数回読み込まれても、中身が一度だけ有効になります。

#pragma once

インクルードガードの代わりに、#pragma once を使うこともあります。

#pragma once

class User {
public:
    void login();
};

#pragma once は、「このヘッダーを一度だけ読み込む」という意味で使われます。

厳密にはC++標準そのものではありませんが、多くの主要なコンパイラで広くサポートされています。

そのため、実務ではよく使われます。

インクルードガードと #pragma once の違い

方法特徴
インクルードガード標準的で移植性が高い
#pragma once簡潔に書ける。多くの環境で使える

移植性を特に重視する場合はインクルードガード、簡潔さを重視する一般的なプロジェクトでは #pragma once が使われることが多いです。

ヘッダーに書いてよいもの・避けたいもの

ヘッダーファイルには、他のファイルから必要になる情報を書きます。

一方で、ヘッダーは多くのファイルから読み込まれるため、書き方を間違えると影響範囲が大きくなります。

ヘッダーに書いてよいもの

ヘッダーに書いてよい代表的なものは次のとおりです。

#pragma once

#include <string>
#include <vector>

class User;
struct Point;
enum class Status;

class Account {
public:
    void login();
};

具体的には、次のようなものです。

書いてよいもの理由
関数宣言他のファイルから関数を呼び出すため
クラス定義クラスの型情報を共有するため
構造体定義データ構造を共有するため
enum / enum class共通の状態や種類を表すため
型エイリアス共通の型名を定義するため
テンプレート定義使う側から実装が見える必要があるため
inline 関数複数の翻訳単位で定義されても扱えるため
inline constexpr 定数ヘッダーで共有しやすいため

ヘッダーに避けたいもの

一方で、次のようなものはヘッダーに不用意に書かない方がよいです。

using namespace std;
int globalValue = 10;
#include <大量の重いヘッダー>

特に避けたいのは、ヘッダー内での using namespace std; です。

悪い例は次のとおりです。

// bad.hpp
#pragma once

#include <string>
using namespace std;

class User {
public:
    string name;
};

このヘッダーを読み込んだすべてのファイルに using namespace std; の影響が広がってしまいます。

その結果、名前の衝突や予期しないコンパイルエラーが起きることがあります。

良い例は次のとおりです。

// good.hpp
#pragma once

#include <string>

class User {
public:
    std::string name;
};

ヘッダーでは、std::stringstd::vector のように名前空間を明示する方が安全です。

前方宣言とは

前方宣言とは、「この名前のクラスが存在する」とだけコンパイラに知らせるための宣言です。

class User;

ヘッダーでは、不要な #include を減らすために前方宣言を使うことがあります。

前方宣言が使えるケース

ポインタや参照だけを使う場合は、クラスの中身まで知らなくてもよいことがあります。

// Message.hpp
#pragma once

class User;

class Message {
public:
    void sendTo(const User& user);
};

この場合、Message.hpp では User の詳細な定義を知る必要がありません。

実装ファイル側で User の中身を使う場合は、.cppUser.hpp を include します。

// Message.cpp
#include "Message.hpp"
#include "User.hpp"

void Message::sendTo(const User& user) {
    // user の中身を使う処理
}

前方宣言が使えないケース

一方で、メンバー変数として実体を持つ場合は、その型のサイズを知る必要があります。

そのため、前方宣言だけでは不十分です。

class User;

class Team {
private:
    User leader; // User の完全な定義が必要
};

この場合は、User.hpp を include する必要があります。

#include "User.hpp"

class Team {
private:
    User leader;
};

また、継承する場合も、基本的には基底クラスの完全な定義が必要です。

#include "Base.hpp"

class Derived : public Base {
};

循環インクルードに注意する

ヘッダー同士が互いに include し合うと、循環インクルードが起きます。

悪い例は次のような形です。

// A.hpp
#pragma once
#include "B.hpp"

class A {
    B b;
};
// B.hpp
#pragma once
#include "A.hpp"

class B {
    A a;
};

このような関係は、コンパイル上の問題だけでなく、設計上の問題にもなります。

AB を値として持ち、BA を値として持つ場合、互いのサイズを決めることができないためです。

循環インクルードを避ける方法

循環インクルードを避けるには、片方をポインタや参照にして、前方宣言を使う方法があります。

// A.hpp
#pragma once

class B;

class A {
public:
    void setB(B* b);

private:
    B* b_;
};
// B.hpp
#pragma once

#include "A.hpp"

class B {
private:
    A a_;
};

このように、必要な場所だけで include するように設計すると、依存関係を減らせます。

inline 関数とヘッダー

ヘッダーに通常の関数定義を書くと、複数の .cpp ファイルから読み込まれたときに重複定義エラーになることがあります。

悪い例は次のとおりです。

// math.hpp
#pragma once

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

このヘッダーを複数の .cpp ファイルから include すると、それぞれの翻訳単位に square の定義が作られます。

その場合は、inline を付けます。

// math.hpp
#pragma once

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

inline を付けることで、ヘッダーに関数定義を書いても扱いやすくなります。

クラス内で定義したメンバー関数

クラス定義の中で直接実装したメンバー関数は、暗黙的に inline 扱いになります。

class Counter {
public:
    int get() const {
        return value_;
    }

private:
    int value_ = 0;
};

このような小さな関数は、ヘッダー内に書かれることも多いです。

テンプレートをヘッダーに書く理由

テンプレートは、普通の関数やクラスとは少し扱いが異なります。

たとえば、次のような関数テンプレートがあるとします。

template <typename T>
T add(T a, T b) {
    return a + b;
}

この関数テンプレートは、実際に使われる型が決まった時点で、具体的な関数が生成されます。

add<int>(1, 2);
add<double>(1.5, 2.3);

この場合、コンパイラは int 用の add と、double 用の add を生成します。

そのため、テンプレートを使う側のファイルから、テンプレートの実装が見えている必要があります。

この理由により、テンプレートは基本的にヘッダーファイルに定義まで書きます。

ヘッダーの依存関係とビルド時間

C++では、ヘッダーの依存関係が増えると、コンパイル時間が長くなりやすくなります。

たとえば、次のようなヘッダーがあるとします。

// UserService.hpp
#pragma once

#include "Database.hpp"
#include "Logger.hpp"
#include "HttpClient.hpp"
#include "JsonParser.hpp"

class UserService {
    // ...
};

この場合、UserService.hpp を include したファイルは、間接的に多くのヘッダーにも依存します。

その結果、少しの変更でも多くのファイルが再コンパイルされる可能性があります。

ヘッダーはできるだけ軽くする

ヘッダーでは、必要最小限の include にすることが大切です。

ポインタや参照だけで済む場合は、前方宣言を使うことで依存を減らせます。

// UserService.hpp
#pragma once

class Database;
class Logger;

class UserService {
public:
    UserService(Database& db, Logger& logger);

private:
    Database& db_;
    Logger& logger_;
};

実装ファイルでは、必要なヘッダーを include します。

// UserService.cpp
#include "UserService.hpp"
#include "Database.hpp"
#include "Logger.hpp"

このようにすると、ヘッダーの依存関係を抑えられます。

Pimplイディオム

C++では、ヘッダーの依存関係を減らすために Pimplイディオム という方法が使われることがあります。

Pimpl は “Pointer to implementation” の略です。

ヘッダーには実装の詳細を出さず、実装クラスへのポインタだけを持たせます。

// Widget.hpp
#pragma once

#include <memory>

class Widget {
public:
    Widget();
    ~Widget();

    void draw();

private:
    class Impl;
    std::unique_ptr<Impl> impl_;
};

実装の詳細は .cpp ファイル側に書きます。

// Widget.cpp
#include "Widget.hpp"

#include <iostream>
#include <vector>
#include <string>

class Widget::Impl {
public:
    std::vector<std::string> data;

    void draw() {
        std::cout << "Drawing widget" << std::endl;
    }
};

Widget::Widget()
    : impl_(std::make_unique<Impl>()) {}

Widget::~Widget() = default;

void Widget::draw() {
    impl_->draw();
}

Pimplのメリット

Pimplを使うと、次のようなメリットがあります。

メリット内容
依存関係を減らせるヘッダーに重い include を書かなくて済む
ビルド時間を短縮しやすい実装変更による再コンパイル範囲を小さくできる
実装詳細を隠せる利用側に内部構造を見せずに済む
ABI安定性に有利ライブラリ設計で役立つことがある

ただし、コードが少し複雑になり、ポインタ経由の管理が必要になるため、必要な場面で使うのがよいです。

ヘッダーオンリーライブラリ

C++には、実装をすべてヘッダーファイルに書くライブラリもあります。

このようなライブラリをヘッダーオンリーライブラリと呼びます。

たとえば、テンプレート中心のライブラリはヘッダーオンリーになりやすいです。

// math.hpp
#pragma once

template <typename T>
T square(T x) {
    return x * x;
}

ヘッダーオンリーライブラリは、.cpp ファイルを別途ビルドしなくても、include するだけで使える点が便利です。

ヘッダーオンリーライブラリのメリット・デメリット

メリットデメリット
導入が簡単コンパイル時間が長くなりやすい
テンプレートと相性がよい実装の詳細が公開される
リンク設定が簡単変更の影響範囲が広い

小規模なユーティリティやテンプレート中心のコードでは便利ですが、大規模なプロジェクトではビルド時間への影響に注意が必要です。

よくあるエラーと対策

ヘッダーファイルでは、いくつか典型的なミスがあります。

インクルードガードを書き忘れる

インクルードガードや #pragma once がないと、同じヘッダーが複数回読み込まれたときに再定義エラーが起きることがあります。

悪い例です。

// bad.hpp

class User {};

良い例です。

// good.hpp
#pragma once

class User {};

または、インクルードガードを使います。

#ifndef USER_HPP
#define USER_HPP

class User {};

#endif

ヘッダーにグローバル変数の定義を書く

通常のグローバル変数定義をヘッダーに書くと、重複定義エラーの原因になります。

悪い例です。

// bad.hpp
#pragma once

int value = 10;

良い例です。

// good.hpp
#pragma once

extern int value;
// good.cpp
#include "good.hpp"

int value = 10;

C++17以降であれば、意図的にヘッダーで定義したい場合に inline 変数を使うこともできます。

// config.hpp
#pragma once

inline int value = 10;

必要な標準ヘッダーを include していない

std::string を使うなら <string>std::vector を使うなら <vector> を include する必要があります。

悪い例です。

// bad.hpp
#pragma once

class User {
private:
    std::string name_;
};

良い例です。

// good.hpp
#pragma once

#include <string>

class User {
private:
    std::string name_;
};

ヘッダーで using namespace std; を使う

ヘッダー内で using namespace std; を書くと、そのヘッダーを include したすべてのファイルに影響します。

悪い例です。

// bad.hpp
#pragma once

#include <string>
using namespace std;

class User {
public:
    string name;
};

良い例です。

// good.hpp
#pragma once

#include <string>

class User {
public:
    std::string name;
};

ヘッダーでは、名前空間を明示する方が安全です。

実務で意識したいヘッダー設計のポイント

C++のヘッダーファイルを書くときは、次のポイントを意識するとよいです。

必ず二重読み込み対策をする

ヘッダーには、#pragma once またはインクルードガードを書きます。

#pragma once

または、

#ifndef SAMPLE_HPP
#define SAMPLE_HPP

// 内容

#endif

これにより、同じヘッダーが複数回読み込まれても再定義エラーを防げます。

ヘッダーには不要な include を書かない

ヘッダーの include は、他の多くのファイルに影響します。

必要なものだけを include し、前方宣言で済む場合は前方宣言を使うと、依存関係を減らせます。

ヘッダーで名前空間を汚染しない

ヘッダーでは、using namespace std; のような書き方は避けるべきです。

代わりに、次のように書きます。

std::string name;
std::vector<int> scores;

実装詳細を出しすぎない

ヘッダーは、多くの場合、そのクラスや関数の公開インターフェースになります。

利用者に見せる必要のない処理や依存関係は、できるだけ .cpp 側に隠すとよいです。

単体で include できるヘッダーにする

良いヘッダーは、そのヘッダー単体を include してもコンパイルできる状態になっています。

たとえば、User.hppstd::string を使っているなら、User.hpp 自身が <string> を include すべきです。

他のヘッダーがたまたま <string> を include していることに依存するのは避けた方がよいです。

まとめ

C++のヘッダーファイルは、主に他のファイルに公開する宣言や型情報を書くためのファイルです。

基本的な考え方は、次のとおりです。

ヘッダーファイル:何が使えるかを書く
ソースファイル:どのように動くかを書く

重要なポイントを整理すると、次のようになります。

ポイント内容
ヘッダーは宣言を書く場所関数、クラス、型、定数などを共有する
#include で読み込むヘッダーの内容が展開される
二重読み込み対策が必要#pragma once またはインクルードガードを使う
宣言と定義を区別する重複定義エラーを防ぐために重要
テンプレートは基本的にヘッダーに書く使う側から実装が見える必要があるため
ヘッダーは軽くするコンパイル時間や依存関係に影響する
using namespace std; は避けるinclude 先に悪影響を与えるため
前方宣言を活用する不要な include を減らせる
必要な標準ヘッダーは自分で include する他のファイルに依存しないため

実務では、まず次の形を基本として覚えるとよいです。

// Sample.hpp
#pragma once

class Sample {
public:
    void run();

private:
    int value_;
};
// Sample.cpp
#include "Sample.hpp"

void Sample::run() {
    value_ = 10;
}

このように、ヘッダーに宣言、ソースに実装という形が、C++の複数ファイル開発の基本です。

ヘッダーファイルは多くのファイルから読み込まれるため、書き方ひとつでコンパイル時間や依存関係に大きく影響します。

だからこそ、ヘッダーはできるだけ分かりやすく、軽く、必要最小限に保つことが大切です。

以上、C++のヘッダーファイルについてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次