STL コンテナの前方宣言:軽いヘッダファイルを作るには

STL (standard template library) コンテナを include すると,それなりにコストが掛かる(参考).このコストは,場合によっては「前方宣言 (forward declaration)」によって大幅に軽減できる.STL コンテナの場合,前方宣言が少々ややこしいので,まとめておく.

前方宣言 (forward declaration)

前方宣言とは,関数やクラスの定義を与えずに,宣言だけ書いておくことだ.関数やクラスを参照するが,その定義までは知る必要ないときは,前方宣言を書いておくだけで済む.例えば,ヘッダファイルに

void func1(const double &x);

とだけ書いておいて,定義は別のファイルに書いても,このヘッダファイルを include するだけで func1 を使うことができる.
次の例も可能だ:

class TTest;
void func2(const TTest &x);

この場合,func2 の宣言では x が TTest クラスのポインタ型であるという情報のみ必要なので,TTest の定義は不要である.もちろん func2 の定義では TTest クラスの中身を参照することになるはずだから*1,TTest の定義が必要となる.
もしこの関数を

TTest a;  // a を「定義」:メモリが確保される
...
func2(a);

のように利用するなら,a を定義する必要があるから,メモリのサイズを知る必要があり,TTest の定義が必要となる.一方,

void func3(const TTest &y)
{
  ...
  func2(y);
  ...
}

こんなふうに,TTest の要素にアクセスすることがないなら,TTest の定義を知る必要はない.

要するに,クラスや構造体を利用する場合,それらの前方宣言のみわかればよい場合があり,そのような場合には定義と前方宣言を別ファイルに分けることによって,ヘッダファイルを軽くできる.例えば標準ライブラリの iosfwd は,クラスの前方宣言だけを集めたヘッダで,軽い.

特に,自分で作るライブラリのヘッダファイルで STLvector や list といったコンテナを使うとき,これらを include せずに済めば,ヘッダファイルの include コストを下げられる.

STL コンテナの前方宣言

さて,STL コンテナの前方宣言は

namespace std
{
template < class T > struct less;
template < class T1, class T2 > struct pair;
template < class T > class allocator;
template < class T, class Allocator > class list;
template < class T, class Allocator > class vector;
template < class Key, class T, class Compare, class Allocator > class map;
}

こんな感じになる.less, pair, allocator の前方宣言は,Allocator や Compare の指定に必要となる.
例えば,

void func1(const std::list<int,std::allocator<int> > &x);

こんなふうに使う.通常,STL コンテナでは Allocator や Compare はデフォルト引数として提供される.しかし,前方宣言でデフォルト引数を定義することはできないので,このように面倒な宣言となる.そこで,

#define LIST_FWD(x_type)  std::list<x_type, std::allocator<x_type> >
#define VECTOR_FWD(x_type)  std::vector<x_type, std::allocator<x_type> >
#define MAP_FWD(x_key,x_value)  std::map<x_key, x_value, std::less<x_key>, std::allocator<std::pair<const x_key,x_value> > >

これらのマクロを定義しておくと,

void func1(const LIST_FWD(int) &x);
void func2(const VECTOR_FWD(int) &x);
void func3(const MAP_FWD(int,double) &x);

簡潔に書ける.

*1:厳密には,x はダミーで func2 の定義中で使用されないかもしれない.この場合,TTest の定義は不要.