[C++]ヘッダファイルとソースファイルを分ける理由と規則

目標

C++のヘッダファイルとソースファイルを分ける際のルールや一般的な方法について理解する。

ヘッダファイルとは

ヘッダファイルとは、クラスの定義や関数の宣言を機能ごとにまとめたファイルです。以下の#include <iostream>や#include “myheader.h”のように#includeに続けて書くことで、そのヘッダファイルに書かれたクラスを使用することができます。

#include <iostream>
#include "myClass.h"
using namespace std;
int main(){
   myClass instance1;
   cout << instance1.getName() << endl;
}

Why are header files necessary?

以下のサンプルコード内のsayHello()やmyClassのように、C++では、使用する変数、クラス、関数などは事前に宣言されている必要があります。

//mysource.cpp

#include <string>
#include <iostream>
using namespace std;
void sayHello(){
    cout << "Hello!" << endl;
}
class myClass {
    private:
        string name;
    public:
        myClass();
        myClass(string name_str);
        string getName();
};
myClass::myClass(){
    name = "nothing";
}
myClass::myClass(string name_str){
    name = name_str;
}
string myClass::getName(){
    return name;
}

int main() {
    sayHello();
    myClass cat1("Joe");
    cout << cat1.getName() << endl;
}
//console result
Hello!
Joe

この宣言を忘れてしまったり、名前を間違えるとエラーとなります。また、複数のファイルで同じ関数やクラスを使用したい場合、何度も宣言しなければなりません。この時にも、名前が統一されていないと分かりにくくなりますし、修正は一括して行いたい場合があります。このような理由から、C++では宣言の部分をメインのソースファイルとは別に、ヘッダファイルとして分割します。

//myClass.h
#include <string>
#include <iostream>

class myClass {
    private:
        std::string name;
    public:
        myClass();
        myClass(std::string name_str);
        std::string getName();
};
myClass::myClass(){
    name = "nothing";
}
myClass::myClass(std::string name_str){
    name = name_str;
}
std::string myClass::getName(){
    return name;
}
//mysource.cpp
#include <iostream>
#include "myClass.h"
using namespace std;
int main() {
    myClass cat1("Joe");
    cout << cat1.getName() << endl;
}
//console result
Joe

#includeについて

#include <filename>または#include “filename”で読み込むことができます。この2つの違いは、#include <filename>では、コンパイラで指定されているライブラリのディレクトリを優先的に探しますが、 #include “filename” では、カレントディレクトリを優先的に探すようになっています。そのため、<iostream>や<string>、<math.h>などの標準ライブラリの場合は#include <filename>を使用し、自作のライブラリヘッダの場合は#include “filename”を使用します。

CおよびC++の標準ライブラリについては以下を参照してください。

ヘッダファイルに書くこと

参照: What to put in a header file from Microsoft C++ language reference

ヘッダファイルには、クラス、メソッド、変数の宣言を書きます。また、クラスに関わる定義ができます。

  • 定義
    • クラスの定義
    • インライン関数の定義
    • マクロの定義
  • 宣言
    • extern 変数宣言
    • typedef宣言
    • グローバル関数の宣言

宣言した関数は、ソースファイルで定義します。ヘッダファイルに組み込んではいけないものを意識すると良いでしょう。例えば、なぜ、変数は宣言のみで、定義してはいけないのか、次の項目で説明します。

ヘッダファイルに書いてはいけないもの

参照: What to put in a header file from Microsoft C++ language reference

重複定義など混乱を生む可能性があるもの

  • ビルトイン型(int, float, stringなど)の定義
  • 無名名前空間
    • つまり名前をつけていない名前空間でクラスなどを定義すること
  • using ディレクティブ
    • usingの使用は、間接的にそのヘッダファイルを読み込んだソースファイルにも適用され混乱しやすい

メモリ領域を確保する要素 (複数のソースファイルで読み込むと、それぞれが同名で別の領域を確保し、重複定義となるため)

  • 非インライン関数定義
    • インライン関数であれば、呼び出し先に展開されるので問題ない
  • 非const変数(定数ではない変数)の定義
    • staticであっても、変数が共有されることはなく、ソースファイルごとに個別に実体が生成されることになる
    • 定数の定義にはconst変数を使う

関数をどのように定義するか?

ヘッダファイルmyClass.hでは、宣言のみを書きます。

//myClass.h
void myFunction();

一緒にコンパイルするいずれかの.cppファイル(メインとなるソースファイルか、ヘッダファイルごとに作成したmyClass.cpp)で、関数を定義します。

//myClass.cpp
#include "myClass.h"
void myFunction(){
    printf("This is my function!");
}

他の.cppファイルでは、myClass.hのみをインクルードすれば、myClass.cppで定義したmyFuction()を使用できます。

変数をどのように定義するか?

こちらも、 ヘッダファイルmyClass.hでは、externのみ行います。

//myClass.h
extern int myVar;

一緒にコンパイルするいずれかの.cppファイル(メインとなるソースファイルか、ヘッダファイルごとに作成したmyClass.cpp)で、グローバル変数を定義します。

//myClass.cpp
#include "myClass.h"
int myVar;

他の.cppファイルでは、myClass.hのみをインクルードすれば、変数を使用することができます。しかし、変数を共通して使う場面は少ないため、あまり推奨できません。C++の場合は、クラス変数を使うと良いでしょう。

インクルードガードを付ける

参照: Include guards from Microsoft C++ language reference

インクルードガードとは、ヘッダファイルを重複してインクルードしないようにするための仕組みです。重複してインクルードする状況は、例えば一つのヘッダファイルAを別のヘッダファイルBでインクルードしたのち、C++ファイルからAとBをインクルードした場合などに起こります。

//myClass.h
#ifndef MYCLASS_H
#define MYCLASS_H
/*myClass.h本体*/
#endif