これまでの章では、すべてのコードを1つの .cpp ファイルに記述してきました。しかし、プログラムが大規模で複雑になるにつれて、このアプローチは現実的ではなくなります。コードの可読性が低下し、少しの変更でもプログラム全体の再コンパイルが必要になり、開発効率が大きく損なわれるからです。
この章では、プログラムを複数のファイルに分割し、それらを効率的に管理・ビルドする方法を学びます。これは、小さなプログラムから一歩進み、本格的なソフトウェア開発を行うための重要なステップです。
C++では、プログラムをヘッダファイルとソースファイルという2種類のファイルに分割するのが一般的です。
.h または .hpp): 「宣言」を置く場所です。クラスの定義、関数のプロトタイプ宣言、定数、テンプレートなどを記述します。他のファイルに対して「何ができるか(インターフェース)」を公開する役割を持ちます。.cpp): 「実装」を置く場所です。ヘッダファイルで宣言された関数の具体的な処理内容などを記述します。ヘッダファイルが公開したインターフェースを「どのように実現するか」を記述する役割を持ちます。簡単な足し算を行う関数を別のファイルに分割してみましょう。
まず、関数の「宣言」をヘッダファイルに記述します。
次に、この関数の「実装」をソースファイルに記述します。
最後に、main関数を含むメインのソースファイルから、このadd関数を呼び出します。
ここで注目すべき点は、math_app.cppがadd関数の具体的な実装を知らないことです。math_utils.hを通じて「intを2つ受け取ってintを返すaddという関数が存在する」ことだけを知り、それを利用しています。
複数のファイルから同じヘッダファイルがインクルードされる状況はよくあります。例えば、A.hがB.hをインクルードし、ソースファイルがA.hとB.hの両方をインクルードするような場合です。
もしヘッダファイルに何の対策もしていないと、同じ内容(クラス定義や関数宣言)が複数回読み込まれ、「再定義」としてコンパイルエラーが発生してしまいます。
この問題を解決するのがインクルードガードです。インクルードガードは、ヘッダファイルの内容が1つの翻訳単位(ソースファイル)内で一度しか読み込まれないようにするための仕組みです。
プリプロセッサディレクティブである #ifndef, #define, #endif を使います。
MATH_UTILS_H は未定義なので、#define が実行され、中身が読み込まれます。MATH_UTILS_H は既に定義されているため、#ifndef から #endif までのすべてが無視されます。マクロ名 (MATH_UTILS_H) は、ファイル名に基づいて一意になるように命名するのが慣習です。
より現代的で簡潔な方法として #pragma once があります。多くのモダンなコンパイラがサポートしています。
この一行をヘッダファイルの先頭に書くだけで、コンパイラがそのファイルが一度しかインクルードされないように処理してくれます。特別な理由がない限り、現在では #pragma once を使うのが主流です。
複数のソースファイル(.cpp)は、それぞれがコンパイルされてオブジェクトファイル(.o や .obj)になります。その後、リンカがこれらのオブジェクトファイルと必要なライブラリを結合して、最終的な実行可能ファイルを生成します。
この一連の作業をビルドと呼びます。ファイルが増えてくると、これを手動で行うのは非常に面倒です。そこで、ビルド作業を自動化するビルドシステムが使われます。
先ほどのmath_app.cppとmath_utils.cppを例に、g++コンパイラで手動ビルドする手順を見てみましょう。
または、以下のように1回のg++コマンドで複数ソースファイルのコンパイルとリンクを同時に行うこともできます。
makeは、ファイルの依存関係と更新ルールを記述したMakefileというファイルに従って、ビルドプロセスを自動化するツールです。
以下は、非常にシンプルなMakefileの例です。
このMakefileがあるディレクトリで、ターミナルからmakeと入力するだけで、必要なコンパイルとリンクが自動的に実行されます。math_app.cppだけを変更した場合、makeはmain.oだけを再生成し、再リンクするため、ビルド時間が短縮されます。
Makefileは強力ですが、OSやコンパイラに依存する部分があり、複雑なプロジェクトでは管理が難しくなります。
CMakeは、MakefileやVisual Studioのプロジェクトファイルなどを自動的に生成してくれる、クロスプラットフォーム対応のビルドシステムジェネレータです。CMakeLists.txtという設定ファイルに、より抽象的なビルドのルールを記述します。
このCMakeLists.txtを使ってビルドする一般的な手順は以下の通りです。
CMakeは、ライブラリの検索、依存関係の管理、テストの実行など、大規模プロジェクトに必要な多くの機能を備えており、現在のC++開発における標準的なツールとなっています。
.h) と、「実装」を記述するソースファイル (.cpp) に分割することで、保守性や再利用性が向上します。
#pragma once や #ifndef/#define/#endif を使用します。make や CMake といったツールが使われます。特に CMake はクロスプラットフォーム開発におけるデファクトスタンダードです。Calculator というクラスを作成してください。このクラスは、加算、減算、乗算、除算のメンバ関数を持ちます。
Calculator.h: Calculatorクラスの定義を記述します。Calculator.cpp: 各メンバ関数の実装を記述します。practice12_1.cpp: Calculatorクラスのインスタンスを作成し、いくつかの計算を行って結果を表示します。これらのファイルをg++で手動ビルドして、プログラムを実行してください。