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

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

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

1 日目の記事は [C++のつまずきポイント解説 その２ - Qiita](http://qiita.com/_EnumHack/items/a341d56a321a668cb21d) を参照。

3 日目の記事は [コンパイル中にコンパイルする「コンパイル時Cコンパイラ」をつくった話 - kw-udonの日記](http://kw-udon.hatenablog.com/entry/2016/12/03/201722) を参照。 3 日目から急に超ハイレベルな内容になっているが [わからないならわからないでいい](https://twitter.com/kaho_sakazaki) 。いや、よくは無いが怖気づかずに分かる所から始めればよい。

[基本編](http://qiita.com/agate_pris/items/1b29726935f0b6e75768) では、クラスの定義とインラインメンバ関数をヘッダファイルに、その他のメンバ関数をソースファイルに記述する事について書いた。応用編では更にヘッダファイルを細かく分割していく。クラス `y` の参照を引数として受け取るメンバ関数 `f` を持つクラス `x` について考える。

```cpp:x.hpp
#ifndef   X_HPP
#define   X_HPP

#include "y.hpp"

class x
{
    // variable
    private:
    int m_value;

    // public function
    public:
    x( int v );
    void f( y const& ) const;
};

inline x::x( int const v ) : m_value( v ) {}

#endif // X_HPP
```

```cpp:x.cpp
#include <iostream>
#include "x.hpp"

void x::f( y const& arg ) const
{
    std::cout << m_value + arg.get() << std::endl;
}
```

```cpp:y.hpp
#ifndef   Y_HPP
#define   Y_HPP

class y
{
    // variable
    private:
    double m_value;

    // public function
    public:
    y( double v );
    double get() const;
};

inline y::y( double const v ) : m_value( v ) {}
inline double y::get() const { return m_value; }

#endif // Y_HPP
```

関数 `x::f` は短い関数なのでヘッダファイルに記述してもよいが、ここでは例としてソースファイルに記述した。ここで再び問題になるのは「コンパイル時間の増大」である。

関数や変数の宣言において、使用する型が参照またはポインタであれば、その型の定義が無くとも宣言のみがあれば問題ない。もし定義まで `include` されるファイルに含まれている場合、対象の型の定義が変更されればその度にコンパイルしなければならない。

そこで、対象の型の宣言のみを別のファイルに記述する。

```cpp:x.hpp
#ifndef   X_HPP
#define   X_HPP

#include "y_fwd.hpp"

class fx
{
    // variable
    private:
    int m_value;

    // public function
    public:
    x( int v );
    void f( y const& ) const;
};

inline x::x( int const v ) : m_value( v ) {}

#endif // X_HPP
```

```cpp:x.cpp
#include <iostream>
#include "x.hpp"
#include "y.hpp"

void x::f( y const& arg ) const
{
    std::cout << m_value + arg.get() << std::endl;
}
```

```cpp:y_fwd.hpp
#ifndef   Y_FWD_HPP
#define   Y_FWD_HPP

class y;

#endif // Y_FWD_HPP
```

```cpp:y.hpp
#ifndef   Y_HPP
#define   Y_HPP

#include "y_fwd.hpp"

class y
{
    // variable
    private:
    double m_value;

    // public function
    public:
    y( double v );
    double get() const;
};

inline y::y( double const v ) : m_value( v ) {}
inline double y::get() const { return m_value; }

#endif // Y_HPP
```

上記のようにする事で `y.hpp` が変更された時でもそれ自身を `include` していなければ `x.hpp` を `include` しているソースファイルの再コンパイルは走らない。

だが、上記の分割だけではコンパイルエラーになってしまう場合がある。それは互いのメンバ関数が互いのメンバ関数を呼ぶ場合、かつその関数の `inline` 指定を行う場合である。クラスの定義がなければメンバ関数を呼ぶことは出来ないが、かといってクラスの定義後に相手のヘッダを `include` するのは不格好であるし、依存関係に対して適切に `include` が行われるとは限らない。そこでクラスを定義するヘッダファイルとメンバ関数を定義するヘッダファイルを分離する。

```cpp:x_fwd.hpp
#ifndef   X_FWD_HPP
#define   X_FWD_HPP

class x;

#endif // X_FWD_HPP
```

```cpp:x_def.hpp
#ifndef   X_DEF_HPP
#define   X_DEF_HPP

#include "x_fwd.hpp"
#include "y_fwd.hpp"

class x
{
    // variable
    private:
    int m_value;

    // public function
    public:
    x( int v );
    int get() const;
    void f( y const& ) const;
};

#endif // X_DEF_HPP
```

```cpp:x_inl.hpp
#ifndef   X_INL_HPP
#define   X_INL_HPP

#include <iostream>
#include "x_def.hpp"
#include "y.hpp"

inline x::x( int const v ) : m_value( v ) {}
inline int x::get() const { return m_value; }

inline void x::f( y const& arg ) const
{
    std::cout << m_value + arg.get() << std::endl;
}

#endif // X_INL_HPP
```

```cpp:x.hpp
#ifndef   X_HPP
#define   X_HPP

#include "x_fwd.hpp"
#include "x_def.hpp"
#include "x_inl.hpp"

#endif // X_HPP
```

```cpp:y_fwd.hpp
#ifndef   Y_FWD_HPP
#define   Y_FWD_HPP

class y;

#endif // Y_FWD_HPP
```

```cpp:y_def.hpp
#ifndef   Y_DEF_HPP
#define   Y_DEF_HPP

#include "y_fwd.hpp"
#include "x_fwd.hpp"

class y
{
    // variable
    private:
    double m_value;

    // public function
    public:
    y( double v );
    double get() const;
    void f( x const& ) const;
};

#endif // Y_DEF_HPP
```

```cpp:y_inl.hpp
#ifndef   Y_INL_HPP
#define   Y_INL_HPP

#include <iostream>
#include "y_def.hpp"
#include "x.hpp"

inline y::y( double const v ) : m_value( v ) {}
inline double y::get() const { return m_value; }

inline void y::f( x const& arg ) const
{
    std::cout << m_value * arg.get() << std::endl;
}

#endif // Y_INL_HPP
```

```cpp:y.hpp
#ifndef   Y_HPP
#define   Y_HPP

#include "y_fwd.hpp"
#include "y_def.hpp"
#include "y_inl.hpp"

#endif // Y_HPP
```

これでお互いのメンバ関数の定義は必用なクラスの定義を参照する事が可能となる。このような複雑な依存関係がなければ `x.hpp` や `y.hpp` を直接 `include` すればよい。