C++ ヘッダとソースでファイルを分ける 基本編

[PR: CADDiではバックエンドエンジニア、フロントエンジニア、アルゴリズムエンジニア、SRE等などを募集しています](https://corp.caddi.jp/recruit/eng)。

この記事は [初心者C++er Advent Calendar 2016 - Adventar](http://www.adventar.org/calendars/1691#list-2016-12-17) の 2 日目の記事である。

1 日目の記事は [Push_backとEmplace_back - Qiita](http://qiita.com/brackss1/items/e92da6458172397f7225) を参照。

応用編は [C++ ヘッダとソースでファイルを分ける 応用編 - Qiita](http://qiita.com/agate_pris/items/d9e4d2ade075dcdb0507) を参照。

閑話休題。 C++ では、クラスの定義とそのメンバ関数の定義とを、ヘッダファイルとソースファイルとで分割するのが一般的である。

```cpp:c.hpp
#ifndef   C_HPP
#define   C_HPP

class c
{
    // variable
    private:
    int m_value;

    // acsessor
    public:
    int get()
    {
        return m_value;
    }
    void set( int const value )
    {
        m_value = value;
    }
};

#endif // C_HPP
```

上記はファイルを分割していない例だ。クラスの定義の中に、関数の定義まで全て記述しているため、ゴチャゴチャとしてしまっている。まずはこれらをヘッダファイル `c.hpp` とソースファイル `c.cpp` に分割する。

```cpp:c.hpp
#ifndef   C_HPP
#define   C_HPP

class c
{
    // variable
    private:
    int m_value;

    // accessor
    public:
    int get();
    void set( int value );
};

#endif // C_HPP
```

```cpp:c.cpp
#include "c.hpp"

int c::get()
{
    return m_value;
}

void c::set( int const value )
{
    m_value = value;
}
```

ヘッダファイルの見た目がグッとシンプルになった。クラス `c` のメンバが一目瞭然である。 [^1]

[^1]: `c.hpp` と `c.cpp` とでメンバ関数 `set` の引数の型が異なっているように見えるが問題無い。引数の型が参照型ではなく値型であれば、関数の定義側で引数の型に `const` を付けるか否かは自由である。これは関数のシグネチャが同じだからである。初心者向けとは言い難いが、 [C++のconstメンバ関数の挙動を直感的に理解する - Qiita](http://qiita.com/takeh1k0/items/b6587734f0a88951f8d4) が参考になる。ここでは、クラスの定義側ではコードの見た目がシンプルになる様 `const` を付けず、メンバ関数の定義側では値が不変である事を明示するために `const` を付けた。

しかしながら、上記のコードには「動作が遅い」という欠点がある。メンバ関数 `get` 及び `set` の内容は極めて短く、通常の関数呼び出しでは実行時のコストが勿体無い。有り体に言えば、 C の構造体のように、直接アクセスした方が高速である、という事だ。この欠点を解消するため、関数の定義を再度ヘッダファイルに戻す。

```cpp:c.hpp
#ifndef   C_HPP
#define   C_HPP

class c
{
    // variable
    private:
    int m_value;

    // accessor
    public:
    int get();
    void set( int value );
};

inline int c::get()
{
    return m_value;
}

inline void c::set( int const value )
{
    m_value = value;
}

#endif // C_HPP
```

ファイルの前半でクラスの定義を行っているが、この時メンバ関数は宣言のみを行い定義は行わない。メンバ関数の定義はクラスの定義の完了後、 `inline` 指定子をつけて行う。 `inline` 指定子は、メンバ関数を呼び出した時、その処理内容を呼び出し箇所に展開する様に指示を行う。 これにより、多くの環境において [^2] データメンバに直接アクセスした場合と同等の処理速度が得られる。また `inline` 指定子を付けなかった場合、複数のファイルでヘッダファイルを `include` していた場合はエラーとなる。 [^3]

[^2]: `inline` 指定子がついている場合でも、必ずしも `inline` 展開されるとは限らない。実際に展開するか否かはコンパイラが決定する。コンパイラのビルドオプションによって、それらの条件をある程度制御可能な場合もある。

[^3]: `include` は基本的に指定されたファイルの内容を記述された箇所にそのまま展開する指令である。よって複数のファイルからヘッダファイルが `include` された場合、メンバ関数の定義が複数箇所に記述されている事になり C++ の単一定義の原則に抵触する。単一定義の原則は One Definition Rule の対訳であり、しばしば ODR と略される。また、これに抵触する事を ODR 違反とも呼ぶ。ここではメンバ関数に `inline` 指定子を付けることで、 ODR 違反を回避する事が出来る。

極短い関数であれば、高速化のためにヘッダ側にメンバ関数の定義を記述する事が望ましいが、長い関数はそもそも通常 `inline` 展開が行われない。それだけでなく、ヘッダファイルに変更が加えられた時、そのヘッダを `include` している全てのソースファイルの再コンパイルが行われるため、頻繁に変更を行う事が予想される部分をヘッダ側に記述するとコンパイル時間を徒に増大させてしまう。よって、基本的に長い関数はヘッダファイルではなくソースファイルにに記述すべきである。 [^4]

[^4]: クラステンプレートのメンバ関数は、長い関数であってもヘッダに定義を記述する。クラステンプレートのメンバ関数及び、関数テンプレートは `inline` 指定子を付けずとも基本的に ODR 違反とはならない。 `inline` 指定子はコンパイラに対する展開指示であるので、テンプレートであっても展開されて欲しい場合には `inline` を付けてよい。また、ビルドせずとも外部から使用できるようにするため、あえて全ての実装をヘッダに書く場合もある。このようなライブラリはしばしばヘッダオンリー等と呼ばれる。

最後に注意点を述べる。本記事で述べた `inline` の用法は C++ における用法である。 `inline` 指定子は C でも利用する事が出来る。当然 C ではクラスの構文は存在しないため、フリー関数での利用になるが C と C++ とでは挙動が異なるため注意深く利用する必用がある。 [^5]

[^5]: 内部結合、外部結合、 `static` 指定子、 `extern` 指定子、 etc 。

応用編は [こちら](http://qiita.com/agate_pris/items/d9e4d2ade075dcdb0507) 。