C++で不要なincludeを減らす

不要なinclude削除

cppでは、includeは単にそこに書かれているファイルの内容を展開するだけになっています。
そのため、includeする量が増えるほどコンパイラが解析する量も増え、
結果としてコンパイル時間が長くなります。

また、キャッシュを利用している場合、
includeされているファイルのどれか一つでも変更があった場合はビルドし直しになるため、
不要なincludeを消すとよりキャッシュを活用できます。

クラスの前方宣言を活用する

クラスのメソッドやプロパティへのアクセスをしない場合、クラスの実態を知る必要はありません。
そのため、メンバ変数にクラスを持つ場合に、ポインタとして持たせることで、
ヘッダファイルにincludeを書く必要が無くなります。
これにより、不要なincludeを減らすことができます。

例えば、以下のようなAクラスがあるとします。

// TestA.h
#include “InClass.h"

class TestA{
public:
  TestA();
  int getNumber();

private:
  InClass m_inclass;
};
//TestA.cpp
#include “TestA.h"

TestA::TestA()
{
};

int TestA::getNumber(){
  return m_inclass.getNumber();
};

このとき、TestAクラスをincludeするクラスは、InClassを使わない場合でも、
TestA.hに書かれているために読み込んでしまいます。
そのため、コンパイラが処理する量が増えるのと、
InClass.hに変更があった場合に使っていないファイルまでコンパイルし直しになります。

ここで、以下のようにクラスの前方宣言を使い、
cppファイル側で読み込むことで、includeをヘッダファイルから削除できます。

//TestA.h
class InClass;

class TestA{
public:
  TestA();
  ~TestA();
  int getNumber();

private:
  InClass* m_inclass;
};
//TestA.cpp
#include “TestA.h"
#include "InClass.h"

TestA::TestA()
{
  m_inclass = new InClass();
};

TestA::~TestA()
{
  delete m_inclass;
};

int TestA::getNumber(){
  return m_inclass->getNumber();
};

これにより、TestAクラスをincludeしているクラスは、InClass.hをincludeしなくなります。
これにより、コンパイラが処理するコード量が減りますし、InClass.hに変更があった場合でも、
TestAクラスだけをコンパイルすれば良くなります。

ただし、メモリをきちんと確保しないと行けないため、デストラクタでの解放忘れなどに注意する必要があります。
また、最近の環境ではそれほど問題にはならないと思いますが、ヒープを確保するのでメモリが断片化します。

Pimplイディオム

classの前方宣言をさらに活用し、privateな物を全て別クラスにして持つ方法です。 これにより、privateの変数を外から完全に見えなくしたり、
内部実装の変更時に外への影響の抑止できます。

以下のようにprivateな変数を持つクラスがあるとします。

//TestA.h
class InClass;
class TestA{
public:
  TestA();
  ~TestA();
  int getNumber();
  int getWeight(int density);

private:
  int height;
  int width;
  int depth;  
  InClass* m_inclass;


  int getVolume();
};

この中で、privateな部分を全て別クラスにしてしまい、メンバ変数をヘッダから消すことが出来ます。

//TestA.h
class TestA{
public:
  TestA();
  ~TestA();
  int getNumber();
  int getWeight(int density);

private:
  class TestApImpl;
  TestApImpl* pImpl;
};
//TestA.cpp
#include "test.h"
#include "InClass.h"

class TestA::TestApImpl{
public:
  TestApImpl() : height(0), width(0), depth(0){
    delete m_inclass;
  }
  ~TestApImpl(){
    delete m_inclass;
  }

  int getVolume(){
    return height*width*depth;
  }

  int height;
  int width;
  int depth;

  InClass* m_inclass;
};

TestA::TestA()
{
  pImpl = new TestApImpl();
};

TestA::~TestA()
{
  delete pImpl;
};

int TestA::getNumber(){
  return pImpl->m_inclass->getNumber();
};

int TestA::getWeight(int density){
  return pImpl->getVolume() * density;
}

これにより実装を外側から完全に隠せるのと、内部の実装変更時にヘッダファイルを変更する必要がなくなり、
コンパイル時のキャッシュを最大限に活用できます。

ただし、一つのcppに複数のクラスが入り、さらに外から見える部分と実際の実装が離れるため、
かなりコードが読みにくくなります。

前方宣言を使えない場合

クラスの実態を持つ場合や、継承したクラスの場合は前方宣言にすることはできません。
このような場合はポインタを持つようにしたり、Decorator パターンによって、
継承を使わなくすることで前方宣言を利用できます。

ただし、それによってコードが読みにくくなると別の問題を引き起こすので、ケースバイケースになります。