トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

ヘッダインクルードを減らそう

なぜか

  • 全能なるCommon.h
// プログラム内のすべてのヘッダをインクルードします。
// この便利なヘッダさえインクルードしておけば、他に面倒なインクルードは必要ありません!

#pragma once  // このファイルの2重インクルード防止

#include "Module1.h"
#include "Module2.h"
#include "Module3.h"
// 以下果てしなく続く

このファイルを(本当に)プログラム中の全ソースからインクルードした場合、
Module1.h内の字下げを少し修正するだけで、全ソースがコンパイルされることになる。
これはまったくの無駄なファイル依存関係といえる。

あるソースファイルが本当に必要としていないヘッダファイルは、インクルードしてはならない。

コンパイル中に優雅に紅茶を飲む楽しみを失いたくなければ、そのままでよいと思われるが、
他人にそれを押し付けたりしないこと。

テクニック

ヘッダに具体的な実装を書かない

関数の定義は、できるだけcppに書く

cppに書いた関数の内容を変更しても、そのcppがコンパイルされるだけで済む。

  • あまり書き換わらないであろうclassメンバのGetSet関数等は、ヘッダに直接書いてよい

定数の数値は、できるだけcppに書く

  • SportsCar.h
#pragma once

class SportsCar
{
    static const int SPEED = 300;
    // etc...
};

上の場合、少しSPEEDの値を変える度に、SportsCar.hをインクルードしているファイルが
すべてコンパイルされる。値が不安定な定数はcppファイル側に置こう。

  • SportsCar.h(改)
#pragma once

class SportsCar
{
    static const int SPEED;// 宣言
    // etc...
};
  • SportsCar.cpp(改というか新規)
#include "SportsCar.h"

const int SportsCar::SPEED = 300;// 定義

こうすると、SPEEDの値を変えても、SportsCar.cpp一つがコンパイルされるだけですむ。
変数でも同様で、「値が必要なら、その場所か名前がわかれば十分」である。

(08/02/17修正)ヘッダファイルで定義できるstaticメンバ変数は、int型に限られる。

クラスや構造体のメンバとして、クラスや構造体を置くときはポインタにする

Partsクラスをメンバに持つBossクラスがあるとする。

  • Sample1
#pragma once

#include "Parts.h"

class Boss
{
    Parts m_Parts;
};

Sample1のように、BossのメンバとしてPartsの実体を持つには、
Partsの定義情報が必要となる。

  • Sample2
#pragma once

class Parts;// 前方参照

class Boss
{
    Parts*  m_pParts;
};

Sample2のようにPartsへのポインタにしておくと、
定義情報がなくとも、Bossのメンバとして持つことができる。
(前方参照によって、Parts型が存在することをコンパイラに教える必要がある)

Partsのポインタからメンバへアクセスする際には、やはりPartsの定義情報は必要になる。
それでも、アクセスするコードをcpp側に書いておくことで、ヘッダ同士の依存関係は弱まることになる。

過剰な適用例

ライブラリのヘッダインクルード手順の煩雑化

ライブラリのモジュールの中には、参照するまでに複雑な手順が必要なものもある。
(あるマクロが定義されていなければ有効にならない、等)
それらの手順を自動的に行うヘッダがあれば、そのヘッダごとインクルードした方がより安全である。

  • WindowsAPIなど

ヘッダ内でのヘッダインクルードの全面禁止

ケース

以下のようなユニットクラスを用意したとする。

  • UnitBase.h
#pragma once

class UnitBase
{
public:
    virtual void Execute()=0;
};
  • PlayerUnit.h
#pragma once
// UnitBase.hは断固インクルードしない。
// EnemyUnitと両方使用した場合、
// #include "UnitBase.h"が2度も書かれることになるからだ。

class PlayerUnit : public UnitBase // 継承にはUnitBaseの情報が必要
{
public:
    virtual void Execute()
    {
        // 主人公ならではの動き
    }
};
  • EnemyUnit.h
#pragma once
// UnitBase.hは断固インクルードしない。
// PlayerUnitと両方使用した場合、
// #include "UnitBase.h"が2度も書かれることになるからだ。

class EnemyUnit : public UnitBase // 継承にはUnitBaseの情報が必要
{
public:
    virtual void Execute()
    {
        // 敵ならではの動き
    }
};

問題点1

PlayerUnitやEnemyUnitを使う側では、必ずUnitBase.hも一緒にインクルードしなければいけない。
この制約により、以下のような記述をコピー&ペーストする面倒な作業が発生し、混乱の元にもなる。

#include "UnitBase.h"  // UnitBaseが未定義だと怒られたのでしぶしぶ(面倒だなぁ・・・)
#include "PlayerUnit.h"

問題点2

仕様変更によりPlayerUnitがUnitBaseの情報を必要としなくなった場合、
無駄な(非常に取り除きにくい)UnitBase.hのインクルードがあちこちに残ってしまう。
本当は少しでも速くコンパイルするための制約だったのに、これでは本末転倒である。

実際はベースクラスが必要無くなるケースなど滅多にないが、
あるヘッダファイルで別ファイルのenum定義を参照し、途中でその参照が必要なくなるケース等は十分にありえる。
その場合でも、上のような制約があれば、あちこちにヘッダインクルードが散らばってしまう。

結論

よほどの事情がない限り、ヘッダは単独でインクルード可能にすべきだと思われる。

おまけ

C++ Coding Standardsによると、
「最近のコンパイラの中には、2重インクルード防止用記述を検出し、同じヘッダを2度オープンさえしないものがある」
とのこと。

Object-Oriented & Java maneuver 別室によると、gccがこれに該当するとのこと。

参考

  1. C++ Coding Standards
  2. Object-Oriented & Java maneuver 別室