C++入門|C++のファイル分割

C++の大きなプログラムは、1つの巻物に全部書かない。設計図、実装、司令塔に分ければ、クラスの修行場はぐっと整理されます。

C++でクラスを使ったプログラムを書き始めると、1つのファイルだけではなく、複数のファイルに分けて管理する場面が増えてきます。

前回までに、Warriorクラスを使って、戦士オブジェクトの考え方を学びました。
そのとき、プログラムは次の3つのファイルに分かれていました。

ファイル役割
warrior.hWarriorクラスの宣言を書く
warrior.cppWarriorクラスのメンバ関数の中身を書く
main.cppWarriorクラスを使ってインスタンスを動かす

このようにファイルを分けることを、ファイル分割と考えると分かりやすいです。

ドラゴンボール風にたとえるなら、1つの修行計画をすべて1枚の巻物に書くのではなく、役割ごとに分けて整理するイメージです。

warrior.h は、戦士の設計図です。
warrior.cpp は、戦士が実際にどう修行するかを書いた修行マニュアルです。
main.cpp は、戦士を呼び出して修行を開始する司令室です。

ファイルドラゴンボール風のイメージC++での役割
warrior.h戦士の設計図クラス宣言
warrior.cpp修行技の実装書メンバ関数の定義
main.cpp修行開始の司令室インスタンス生成と利用

C++のファイル分割では、ファイル同士の関係を理解することが大切です。
特に、どのファイルがどのファイルを読み込むのか、なぜヘッダーファイルが必要なのか、そして2重インクルードをどう防ぐのかを押さえておきましょう。

図:C++のファイル分割は役割ごとの修行巻物

この図が示していること

この図では、C++のプログラムを3つのファイルに分ける考え方を表しています。

warrior.hにはWarriorクラスの宣言を書きます。
warrior.cppにはWarriorクラスのメンバ関数の中身を書きます。
main.cppではWarriorクラスのインスタンスを作って利用します。

ファイルごとに役割を分けることで、プログラム全体の見通しがよくなります。

ファイル分割が必要になる理由

小さなプログラムであれば、main.cppだけにすべてを書いても動かすことはできます。

しかし、クラスが増えたり、メンバ関数が増えたりすると、1つのファイルにすべてを書くのは読みづらくなります。

たとえば、Warriorクラスに次のような内容が増えていったとします。

追加される内容
メンバ変数power、trainingTime、style
メンバ関数train、move、guard、attack
利用する処理main関数で複数の戦士を作る

これらをすべてmain.cppに書くと、main.cppがどんどん長くなってしまいます。

そこで、C++では次のように役割を分けます。

  • クラスの形はヘッダーファイルに書く
  • メンバ関数の中身はcppファイルに書く
  • 実際に使う処理はmain.cppに書く

ドラゴンボール風に言えば、戦士の設計図、修行技の説明、実戦での使い方を別々の巻物に分けるようなものです。

これにより、どこに何が書かれているのかが分かりやすくなります。

warrior.hの役割

warrior.hは、Warriorクラスの宣言を書くファイルです。

プロジェクト/ファイル名: Chap3_01/warrior.h

#ifndef _WARRIOR_H_
#define _WARRIOR_H_

class Warrior {
public:
    // 戦闘力
    double power;

    // 修行する
    void train(double hour);
};

#endif // _WARRIOR_H_

このファイルには、Warriorクラスがどのようなメンバを持つのかが書かれています。

メンバ種類内容
powerメンバ変数戦士の戦闘力
train(double hour)メンバ関数指定した時間だけ修行する

ヘッダーファイルには、クラスの設計図を書きます。
ここでは、trainがどんな処理をするのかまでは詳しく書いていません。

trainというメンバ関数があることだけを宣言しています。

warrior.cppの役割

warrior.cppは、Warriorクラスのメンバ関数の中身を書くファイルです。

プロジェクト/ファイル名: Chap3_01/warrior.cpp

#include "warrior.h"
#include <iostream>

using namespace std;

// 修行する
void Warrior::train(double hour) {
    cout << "戦闘力" << power << "の戦士が" << hour << "時間修行" << endl;
    cout << power * hour << "ポイント成長しました。" << endl;
}

このファイルでは、Warrior::trainの具体的な処理を書いています。

void Warrior::train(double hour)

この書き方は、Warriorクラスに属するtrainメンバ関数を定義するという意味です。

warrior.cppでは、Warriorクラスの情報が必要になります。
そのため、先頭でwarrior.hを読み込んでいます。

#include "warrior.h"

warrior.hを読み込むことで、コンパイラはWarriorクラスの存在や、power、trainの宣言を知ることができます。

main.cppの役割

main.cppは、プログラムの実行開始点です。

ここでは、Warriorクラスからインスタンスを作り、メンバ変数とメンバ関数を使います。

プロジェクト/ファイル名: Chap3_01/main.cpp

#include <iostream>
#include "warrior.h"

using namespace std;

int main(int argc, char** argv) {
    // インスタンスを生成する
    Warrior warrior;

    // 戦闘力を設定する
    warrior.power = 80;

    // 指定した時間だけ修行する
    warrior.train(1.5);

    // プログラムが正常に終わったことを示す
    return 0;
}

main.cppでも、Warriorクラスを使うためにwarrior.hを読み込んでいます。

#include "warrior.h"

この読み込みがあるからこそ、main.cppでは次のように書けます。

Warrior warrior;
warrior.power = 80;
warrior.train(1.5);

main.cppは、Warriorクラスの設計を知っている必要があります。
その設計を知らせる役割を持つのが、warrior.hです。

ファイルの依存関係

Chap3_01の3つのファイルは、それぞれ独立しているように見えますが、実際には関係があります。

関係を整理すると、次のようになります。

+--------------+          +-------------+
|   main.cpp   |   →      |  warrior.h  |
+--------------+          +-------------+
 Warriorクラスを利用       Warriorクラスの宣言
                               ↑
+--------------+               |
| warrior.cpp  |   ------------+
+--------------+
 Warriorクラスの実装

main.cppは、Warriorクラスを使うためにwarrior.hを読み込みます。
warrior.cppは、Warriorクラスのメンバ関数を定義するためにwarrior.hを読み込みます。

つまり、warrior.hは、main.cppとwarrior.cppの両方から参照される重要なファイルです。

ファイルwarrior.hを読み込む理由
main.cppWarriorクラスを使うため
warrior.cppWarriorクラスのメンバ関数を定義するため

ドラゴンボール風にたとえるなら、warrior.hは戦士の設計図です。
main.cppは、その設計図を見て戦士を呼び出します。
warrior.cppは、その設計図を見て修行技の中身を書きます。

図:warrior.hはmain.cppとwarrior.cppから参照される

この図が示していること

この図では、warrior.hがmain.cppとwarrior.cppの両方から読み込まれる関係を表しています。

main.cppは、Warriorクラスを使うためにwarrior.hを必要とします。
warrior.cppは、Warrior::trainを定義するためにwarrior.hを必要とします。

このように、ヘッダーファイルは複数のcppファイルから共有される設計図のような役割を持ちます。

#includeはファイルを読み込む仕組み

C++では、別のファイルに書かれた宣言を使いたいとき、#includeを使います。

#include "warrior.h"

これは、warrior.hに書かれた内容を、このファイルで使えるようにするための記述です。

main.cppでは、Warriorクラスを使うためにwarrior.hを読み込みます。

warrior.cppでは、Warrior::trainを定義するためにwarrior.hを読み込みます。

つまり、#includeは、必要な設計図を読み込むための入口です。

ドラゴンボール風に言えば、修行の前に戦士の設計図を確認するようなものです。
設計図を読まなければ、Warriorというクラスがどのようなメンバを持っているのか分かりません。

2重インクルードとは何か

C++のファイル分割で特に注意したいのが、2重インクルードです。

2重インクルードとは、同じヘッダーファイルが複数回読み込まれてしまうことです。

たとえば、warrior.hがmain.cppとwarrior.cppから読み込まれるのは自然なことです。
ただし、プログラムが大きくなってくると、あるヘッダーファイルが別のヘッダーファイルからも読み込まれ、結果として同じ内容が何度も読み込まれることがあります。

もし同じクラス宣言が何度も読み込まれると、コンパイラから「同じクラスが複数回定義されています」と判断され、エラーになることがあります。

ドラゴンボール風にたとえるなら、同じ戦士の設計図を修行場に何枚も重ねて提出してしまい、司令室が「同じ戦士が何度も登録されている」と混乱するようなものです。

2重インクルードを防ぐヘッダーガード

2重インクルードを防ぐために、ヘッダーファイルにはヘッダーガードを書きます。

warrior.hでは、次の部分がヘッダーガードです。

#ifndef _WARRIOR_H_
#define _WARRIOR_H_

class Warrior {
public:
    double power;
    void train(double hour);
};

#endif // _WARRIOR_H_

この書き方によって、warrior.hの内容が2回以上読み込まれることを防ぎます。

記述役割
#ifndef WARRIOR_H_WARRIOR_H_がまだ定義されていないか確認する
#define WARRIOR_H_WARRIOR_H_を定義する
#endifヘッダーガードの範囲を終える

この仕組みにより、1回目はクラス宣言が読み込まれます。
2回目以降は、すでに_WARRIOR_H_が定義されているため、同じクラス宣言は読み込まれません。

#ifndefの意味

#ifndefは、指定したキーワードがまだ定義されていなければ、その下の処理を有効にするという意味です。

#ifndef _WARRIOR_H_

これは、_WARRIOR_H_がまだ定義されていないかを確認しています。

1回目にwarrior.hが読み込まれたとき、_WARRIOR_H_はまだ定義されていません。
そのため、#ifndefから#endifまでの内容が有効になります。

そして、次の行で_WARRIOR_H_が定義されます。

#define _WARRIOR_H_

これにより、2回目以降に同じファイルが読み込まれたときは、すでに_WARRIOR_H_が定義済みになります。

その結果、#ifndefから#endifまでの中身は読み込まれません。

#defineの意味

#defineは、指定したキーワードを定義するためのプリプロセッサ指令です。

#define _WARRIOR_H_

これにより、_WARRIOR_H_というキーワードが定義された状態になります。

ヘッダーガードでは、この定義済みかどうかを利用して、同じヘッダーファイルが再び読み込まれるのを防ぎます。

ドラゴンボール風にたとえるなら、1回目に設計図を受け付けたときに、「Warrior設計図は登録済み」という札を立てるようなものです。

2回目に同じ設計図が来たときには、その札を見て「もう登録済みなので読み込まない」と判断します。

#endifの意味

#endifは、#ifndefで始まった条件範囲の終わりを示します。

#endif // _WARRIOR_H_

この行までが、ヘッダーガードの有効範囲です。

つまり、次のような形で、クラス宣言全体を囲んでいます。

#ifndef _WARRIOR_H_
#define _WARRIOR_H_

// 2重インクルードを防ぎたい内容

#endif // _WARRIOR_H_

この範囲の中に、Warriorクラスの宣言を書いておくことで、同じ宣言が何度も読み込まれないようにできます。

ヘッダーガードの1回目と2回目の動き

ヘッダーガードの動きを、1回目と2回目で分けて考えると分かりやすいです。

1回目にwarrior.hを読み込む場合

#ifndef _WARRIOR_H_
→ _WARRIOR_H_はまだ定義されていない
→ 中身を読み込む

#define _WARRIOR_H_
→ _WARRIOR_H_を定義する

class Warrior { ... };
→ Warriorクラスの宣言を読み込む

#endif
→ ヘッダーガード終了

1回目は、まだ_WARRIOR_H_が定義されていないため、Warriorクラスの宣言が読み込まれます。

2回目以降にwarrior.hを読み込む場合

#ifndef _WARRIOR_H_
→ _WARRIOR_H_はすでに定義されている
→ 中身を読み込まない

#endif
→ ヘッダーガード終了

2回目以降は、すでに_WARRIOR_H_が定義されています。
そのため、クラス宣言は読み込まれません。

これにより、同じクラスが2回宣言されることを防げます。

図:ヘッダーガードで2重インクルードを防ぐ

この図が示していること

この図では、ヘッダーガードによって2重インクルードを防ぐ流れを表しています。

1回目にwarrior.hが読み込まれると、_WARRIOR_H_がまだ定義されていないため、Warriorクラスの宣言が読み込まれます。
その途中で#define _WARRIOR_H_が実行され、_WARRIOR_H_が定義済みになります。

2回目以降にwarrior.hが読み込まれると、すでに_WARRIOR_H_が定義されているため、クラス宣言は読み込まれません。
これにより、同じクラスが何度も宣言されることを防げます。

ヘッダーガードの名前の付け方

ヘッダーガードで使うキーワードは、ファイル名をもとに大文字で作ることがよくあります。

今回のファイル名はwarrior.hなので、次のようにしています。

_WARRIOR_H_

一般的には、ヘッダーファイル名を大文字にし、ドットをアンダースコアに置き換えたような名前にします。

ファイル名ヘッダーガードの例
warrior.hWARRIOR_H
battle.hBATTLE_H
training_menu.hTRAINING_MENU_H

ただし、プロジェクト全体で重複しない名前にすることが大切です。

同じヘッダーガード名を別のヘッダーファイルでも使ってしまうと、本来読み込まれるべきファイルが読み込まれなくなる可能性があります。

#pragma onceという書き方

最近のC++コンパイラでは、ヘッダーファイルの先頭に次のように書くだけで、2重インクルード防止ができる場合があります。

#pragma once

この書き方はとても短く、見た目も分かりやすいです。

#pragma once

class Warrior {
public:
    double power;
    void train(double hour);
};

ただし、C++の基本学習では、#ifndef、#define、#endifを使ったヘッダーガードの仕組みを理解しておくことが大切です。

なぜなら、古いコードや多くの教材、既存のプロジェクトでは、今でもヘッダーガードがよく使われているからです。

書き方特徴
#ifndef / #define / #endif仕組みが明確で、多くの環境で使われる
#pragma once短く書けるが、コンパイラ依存の側面がある

まずはヘッダーガードの仕組みを理解し、そのうえで#pragma onceも読めるようにしておくと安心です。

ファイル分割で押さえたいポイント

C++のファイル分割では、次のポイントを押さえることが大切です。

ポイント内容
ヘッダーファイルクラス宣言を書く
cppファイル関数やメンバ関数の中身を書く
main.cppプログラムの実行開始点を書く
#include必要なヘッダーファイルを読み込む
ヘッダーガード同じヘッダーが何度も読み込まれるのを防ぐ

ドラゴンボール風にいえば、warrior.hは戦士の設計図、warrior.cppは修行技の実装書、main.cppは修行を開始する司令室です。

この3つの役割を分けることで、クラスを使ったC++プログラムを整理しやすくなります。

特に、ヘッダーファイルは複数のcppファイルから読み込まれることが多いため、2重インクルード防止がとても重要です。

C++のファイル分割を読むときの考え方

C++のファイル分割を読むときは、まず次の流れを意識しましょう。

読む順番見る内容
1ヘッダーファイルでクラスの形を見る
2cppファイルでメンバ関数の中身を見る
3main.cppでインスタンスの使われ方を見る

Warriorクラスなら、まずwarrior.hを見て、powerとtrainがあることを確認します。

次にwarrior.cppを見て、trainがどのような処理をするのかを確認します。

最後にmain.cppを見て、Warriorインスタンスがどのように生成され、どのメンバが使われているかを確認します。

この順番で読むと、ファイルが分かれていても迷いにくくなります。

C++では、プログラムが大きくなるほどファイル分割が重要になります。
設計図、実装、利用の場所を分けて整理することで、クラスを使ったプログラムがぐっと読みやすくなります。