C++では、宣言と定義を分けて書くことがよくあります。 宣言はヘッダーファイルと呼ばれるファイルにまとめ、それに対応する実装はソースファイルに置きます。 ヘッダーファイルはAPIだと思うとよいでしょう。 ヘッダーファイルを見れば、そのコードベースが_何_を提供しているのかがわかります。_どのように_実装されているかの詳細までは示しません。
ヘッダーファイルの拡張子として最もよく使われるのは.hです。
プロジェクトによっては.hppを使ったり、拡張子をまったく付けなかったりします。
定義は別の.cppファイルに書きます。
両者を再び結び付けるために、ソースファイルは対応するヘッダーファイルを_インクルード_するところから始まります。
"quick_math"という名前のライブラリを書いて、その中によく使う"super_root"という関数を用意したい場合、ファイルは次のようになります。
// A file named quick_math.h
#pragma once
namespace quick_math {
double super_root(double x, int n);
}
// A file named quick_math.cpp
#include <cmath>
#include "quick_math.h"
double quick_math::super_root(double x, int n) {
while (n) {
x = std::sqrt(x), --n;
}
return x;
}
実装でだけ必要になるヘッダーをインクルードする場合は、対応する#includeの行はソースファイルにだけ書けば済みます。
ヘッダーでインクルードしたものは、.cppファイルでもそのまま使えます。たとえば、次の例のstringライブラリがそうです。
注意: ;はヘッダーファイルの宣言のあとには必要ですが、ソースファイルの定義のあとには必要ありません。
ExercismのC++の演習の多くは、ほぼ空の2つのファイル(ヘッダーとソース)から始まります。
演習を解くには、*_test.cppファイルを確認して、期待されている関数の名前と名前空間を調べる必要があります。
クラスはとても複雑になることがあり、ヘッダーとソースの分担との関係はわかりにくく感じられるかもしれません。 構成のひとつとして、実装の詳細はすべてソースファイルに置き、宣言とメンバー変数はすべてヘッダーに置くという方法があります。
// A file named robot_flower.h
#if !defined(ROBOT_FLOWER_H)
#define ROBOT_FLOWER_H
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0);
void give_water();
std::string get_name();
int get_size();
void start_next_day();
};
} // namespace robots
#endif
// A file named robot_flower.cpp
#include "robot_flower.h"
robots::Flower::Flower(std::string name, int size) {
this->name = "Robotica " + name;
this->size = size;
}
void robots::Flower::start_next_day() {
if (!needs_water) ++size;
needs_water = true;
}
std::string robots::Flower::get_name() { return name; }
int robots::Flower::get_size() { return size; }
ヘッダーをAPIの概要として使う場合、デフォルト値のような情報を探すのはこのヘッダーです。
そのため、コンストラクターのsize仮引数のデフォルト値は、実装側ではなくヘッダー側で指定します。
ソースファイルの定義には、名前空間robotsとクラスFlowerを先頭に付けます。
もうひとつの構成は、.cppファイルをまったく持たない_ヘッダーのみ_のライブラリです。
// A file named robot_flower.h
#pragma once
#include <string>
namespace robots {
class Flower {
private:
bool needs_water{};
int size{};
std::string name{};
public:
Flower(std::string name, int size = 0) {
this->name = "Robotica " + name;
this->size = size;
}
void give_water() { needs_water = false; }
std::string get_name() { return name; }
int get_size() { return size; }
void start_next_day() {
if (!needs_water) ++size;
needs_water = true;
}
};
} // namespace robots
プロジェクトではこれらの構成を組み合わせて使うこともあり、それぞれの用途に何が最適かについては多くの議論があります。
上のヘッダーファイルの例にあった#pragma onceの行にお気づきになったかもしれません。
これはインクルードガードと呼ばれ、コンパイルの間にそのファイルの内容が一度だけインクルードされるようにして、エラーを防ぎます。
インクルードガードには、#ifndefで始まり#endifで終わる、より複雑な書き方もあります。詳しくは後述します。
C++のコードは手続き的に評価されます。 関数を使いたいときは、その時点でコンパイラーがその関数を知っている必要があります。 ソースコード内の定義を一直線に並べられないこともあります。 次の例を見てみましょう。
int myFunction(int n) {
if (n < 10) {
return n;
} else {
return myOtherFunction(n / 10);
}
}
int myOtherFunction(int m) { return myFunction(m / 2); }
myFunctionを定義する時点では、コンパイラーはまだmyOtherFunctionを知りません。
残念ながら、この相互参照の問題は順番を入れ替えても解決できません。
C++には前方宣言があり、myFunctionとmyOtherFunctionを定義する前にコンパイラーに知らせることができます。
コンパイラーは、宣言のあとのどこかで定義が続くとみなします。
次の例は、関数の前方宣言の使い方を示しています。
int myFunction(int n); // Forward declaration of myFunction
int myOtherFunction(int m); // Forward declaration of myOtherFunction
// Definition of myFunction
int myFunction(int n) {
if (n < 0) {
return 0;
} else {
return myOtherFunction(n - 2);
}
}
// Definition of myOtherFunction
int myOtherFunction(int m) { return myFunction(m / 2); }
プロジェクト内で同じファイルが何度インクルードされてもかまいません。 ヘッダーファイルに定義を書くべきではありません。 プロジェクト全体で、同じ定義を複数持つことはできません。 これは「One definition rule」と呼ばれます。 これはコンパイラーによって強制されます。
インクルードガードを使えば、同じ定義が意図せず複数回インクルードされるのを簡単に防げます。
インクルードガードは、コンパイルの各段階で特別な手順によって機能します。
ねらいは、ある変数がまだ設定されていないときだけファイルをインクルードし、インクルードした時点でその変数を設定することです。
この変数の名前には、ファイル名を変化させたものがよく使われます。
ほかの方法として、同じ名前を誤って2回使う危険を減らすためにUUIDを生成するやり方もあります。
構文は次のとおりです。ここではMY_HEADER_FILE_Hを変数として使っています。
#ifndef MY_HEADER_FILE_H /* any name uniquely mapped to file name */
#define MY_HEADER_FILE_H
// file content
#endif
#pragma onceの問題は、プラグマがC++言語の公式な一部ではなく、その実装がコンパイラーごとに異なることです。
多くの大規模プロジェクトはより簡単なプラグマの方法に切り替えていますが、一部はいまも慎重です。