PR: CADDiではバックエンドエンジニア、フロントエンジニア、アルゴリズムエンジニア、SRE等などを募集しています。
この記事は C++ Advent Calendar 2016 - Adventar の 2 日目の記事である。
1 日目の記事は C++のつまずきポイント解説 その2 - Qiita を参照。
3 日目の記事は コンパイル中にコンパイルする「コンパイル時Cコンパイラ」をつくった話 - kw-udonの日記 を参照。 3 日目から急に超ハイレベルな内容になっているが わからないならわからないでいい 。いや、よくは無いが怖気づかずに分かる所から始めればよい。
基本編 では、クラスの定義とインラインメンバ関数をヘッダファイルに、その他のメンバ関数をソースファイルに記述する事について書いた。応用編では更にヘッダファイルを細かく分割していく。クラス y
の参照を引数として受け取るメンバ関数 f
を持つクラス x
について考える。
#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
#include <iostream>
#include "x.hpp"
void x::f( y const& arg ) const
{
std::cout << m_value + arg.get() << std::endl;
}
#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
されるファイルに含まれている場合、対象の型の定義が変更されればその度にコンパイルしなければならない。
そこで、対象の型の宣言のみを別のファイルに記述する。
#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
#include <iostream>
#include "x.hpp"
#include "y.hpp"
void x::f( y const& arg ) const
{
std::cout << m_value + arg.get() << std::endl;
}
#ifndef Y_FWD_HPP
#define Y_FWD_HPP
class y;
#endif // Y_FWD_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
が行われるとは限らない。そこでクラスを定義するヘッダファイルとメンバ関数を定義するヘッダファイルを分離する。
#ifndef X_FWD_HPP
#define X_FWD_HPP
class x;
#endif // X_FWD_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
#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
#ifndef X_HPP
#define X_HPP
#include "x_fwd.hpp"
#include "x_def.hpp"
#include "x_inl.hpp"
#endif // X_HPP
#ifndef Y_FWD_HPP
#define Y_FWD_HPP
class y;
#endif // Y_FWD_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
#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
#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
すればよい。