導入
1つの .cpp ファイルにすべてを書き続けると、プログラムが大きくなるほど見通しが悪くなります。実務の C++ では、外部に見せる「宣言」をヘッダファイル(.h)に、実際の処理である「定義」を実装ファイル(.cpp)に分けて書きます。
この回は読み物です。ブラウザの学習環境は単一ファイルなので、複数ファイルへの分離は手元の環境(g++ / clang など)で試してください。
説明
まず「宣言」と「定義」の違いを区別します。宣言は「名前と型(プロトタイプ)だけ」、定義は「実際の中身(実体)」です。
double circleArea(double r); // 宣言:名前・引数・戻り値の型だけ
double circleArea(double r) { // 定義:中身(実体)がある
return 3.14159 * r * r;
}
ヘッダファイルには宣言だけを置き、複数の .cpp ファイルから #include して共有します。第17章の namespace を使い、宣言・定義の両方を名前空間の中に入れます。
// geometry.h(宣言だけを置くヘッダファイル)
#pragma once // 二重インクルード防止(今どきはこれで十分)
namespace geometry {
double circleArea(double r); // 宣言のみ。中身はここには書かない
}
// geometry.cpp(定義=実体を置く実装ファイル)
#include "geometry.h"
namespace geometry {
double circleArea(double r) { // ClassName::method() 同様、名前空間::関数名で定義
return 3.14159 * r * r;
}
}
// main.cpp(geometry.h を include して使う側)
#include <iostream>
#include "geometry.h"
using namespace std;
int main() {
cout << geometry::circleArea(2.0) << endl; // 12.5664
return 0;
}
#pragma once は「このファイルは一度しか読み込まない」という合図です。同じヘッダを複数の .cpp から #include しても、二重に取り込まれて壊れることを防ぎます。古いコードでは #ifndef GEOMETRY_H / #define GEOMETRY_H / #endif という書き方(インクルードガード)も使われますが、意味は同じです。
クラスも同じ考え方で分離します。ヘッダにはクラス定義(メンバの一覧)を、メンバ関数の実体は .cpp に クラス名::関数名(){ ... } の形で書きます。
// shape.h
#pragma once
namespace shapes {
class Circle {
public:
Circle(double r); // コンストラクタの宣言
double area() const; // メンバ関数の宣言
private:
double radius;
};
}
// shape.cpp
#include "shape.h"
namespace shapes {
Circle::Circle(double r) : radius(r) {} // コンストラクタの定義
double Circle::area() const { // メンバ関数の定義
return 3.14159 * radius * radius;
}
}
flowchart LR h["geometry.h(宣言)"] h --> mcpp["main.cpp"] h --> gcpp["geometry.cpp"] mcpp --> mo["main.o(コンパイル)"] gcpp --> go["geometry.o(コンパイル)"] mo --> link["リンク"] go --> link link --> exe["実行ファイル"]
各 .cpp ファイルは「コンパイル単位」として個別にコンパイルされ、.o(オブジェクトファイル)になります。最後に「リンカ」がすべての .o を集めて1つの実行ファイルにまとめます。関数を呼ぶ側(main.cpp)は、コンパイルの時点では geometry.h の宣言さえ見えていれば十分で、実体はリンクの段階で結びつけられます。
この仕組みを支えるのが ODR(One Definition Rule/1定義規則) です。「同じものの定義(実体)は、実行ファイル全体でちょうど1つでなければならない」というルールで、宣言(ヘッダに置く)はいくつの .cpp に取り込まれても構いませんが、定義(実体)を誤ってヘッダに書いて複数の .cpp から #include すると、リンク時に「二重定義」エラーになります(inline 関数やテンプレートは例外です)。
まとめ
ヘッダファイル(.h)には宣言、実装ファイル(.cpp)には定義を置きます。#pragma once(またはインクルードガード)で二重インクルードを防ぎます。各 .cpp は個別にコンパイルされて .o になり、リンカが1つの実行ファイルにまとめます。ODR(1定義規則)は「実体は1つだけ」という約束です。次のレッスンでは、こうして整理した設計の中で使う、複数の型を1つで扱う道具を見ていきます。