[ C++で開発 ]

ヘッダファイルの依存を減らす

C++では、ヘッダファイルにクラスの実装詳細(非公開のメンバー他)を記述します。このヘッダファイルはクラスの利用者がインクルードして使用します。そのため、クラスの利用法(インタフェース)は全く変更がなくても、クラスを利用しているソースまでコンパイルし直す必要があります。また、クラスが内部的に利用しているライブラリがあったとしても、そのライブラリのヘッダファイルが間接的にクラスの利用者にインクルードされるため、コンパイル時に多大な結合が発生します。

アプリケーションを開発するときに、利用したライブラリが内部で利用している別なライブラリの環境までちゃんと用意しないとえらいことになります。どこかに変更があってもえらいことになります。まったくC++はどこかにちょっとでも変更があれば、すべてのソースを再ビルドせよ、ということに他なりません。

これでは、「C++で大きなプログラムを作ることはできないじゃないか」と声を大にして言いたくなります。

C++では、ヘッダーファイルはprivateであっても利用者と提供者との間の契約です。

絶縁(insulation)

この問題を解決するための方法に絶縁があります。以下の書籍で紹介している方法です。

「大規模C++ソフトウェアデザイン」 ジョン・ラコス 著, 滝沢 徹+牧野 祐子 訳、ピアソン、1999.6
http://www.pearsoned.co.jp/washo/prog/wa_pro15-j.html

絶縁とは、不要なコンパイル時結合を回避することです。絶縁には以下の方法があります。

  1. 継承をコンポジションに変更
  2. 埋め込みデータメンバーはポインタに変更
  3. プロトコルクラス
  4. 完全絶縁具体クラス
  5. 単一コンポーネントラッパー
  6. マルチコンポーネントラッパー

「4.完全絶縁具体クラス」と同様の解決方法については、他にもいろいろな書籍で取り上げられています。

継承をコンポジションに変更

#includeでヘッダファイルを取り込む記述をクラスを前方宣言とすることで依存性を低減します。継承の場合、必ずヘッダファイルを取り込まなくてはなりません。継承をコンポジションにすることによってヘッダファイルを取り込む必要がなくなります。

埋め込みデータメンバーはポインタに変更

データメンバー(実体)は、その型を定義したヘッダーファイルを#includeで取り込まなくてはなりません。

そこで、データメンバーを実体ではなくポインタに変更することで、#includeでヘッダーファイルを取り込まずにデータメンバーの型を前方宣言(forward declaration)とすることができます。これによって#includeの依存性を低減します。

前方宣言は実体の定義をよく知る必要がある

しかしながら前方宣言はその型がclassとして定義されているのかtypedefなのか、templateなのかを調べて、実際の定義に合わせて宣言を書く必要があります。特にtemplateのtypedefとなるとやっかいです。そのため、前方宣言で逃げれないケースも多々発生します。

プロトコルクラス

以下の条件を持つクラス

  1. どんな種類のメンバデータ、非仮想関数あるいはprivate(protected)メンバーの入ったクラスを含まず、そうしたクラスからの継承もない
  2. 空実装の定義された非インラインの仮想デストラクタを持つ
  3. 継承関数を含むデストラクタ以外の全メンバ関数が純粋仮想関数として宣言され、未定義のままになっている

Java言語におけるinterfaceと似た使用方法です。

実装例

プロトコルクラスを定義します。

Character.h
#pragma once

#include <string>
#include <boost/shared_ptr.hpp>

using std::string;
using boost::shared_ptr;

namespace  character {

class Character {
public:
    virtual ~Character();
    virtual const string getName() const = 0;
    virtual void setName(const string& aName) = 0;

    static shared_ptr<Character> create(const std::string& aName);
};

}

プロトコルクラスの実装クラスを定義します。

ConcreteCharacter.h
#pragma once

#include "Character.h"
#include <string>

using std::string;

namespace  character {

class ConcreteCharacter : public Character {
public:
    ConcreteCharacter(const std::string& aName) : name(aName);
    virtual ~ConcreteCharacter();

    virtual const string getName() const;
    virtual void setName(const string& aName);

private:
    string name;
};

}

プロトコルクラスの実装クラスの実装を記述します。

ConcreteCharacter.cpp
#pragma once

#include "ConcreteCharacter.h"
#include <string>
#include <boost/shared_ptr.hpp>

using std::string;
using boost::shared_ptr;

namespace  character {

ConcreteCharacter::ConcreteCharacter(const std::string& aName)
 : name(aName) {}
ConcreteCharacter::~ConcreteCharacter() {}

virtual const string ConcreteCharacter::getName() const {
    return name;
} 

virtual void ConcreteCharacter::setName(const string& aName) {
    name = aName;
}

shared_ptr<Character> Character::create(const string& aName) {
    return shared_ptr<Character>(new ConcreteCharacter(aName));
}

}

完全絶縁具体クラス、またの名をPimplイディオム

コンポジションのテクニックを使う。

Pimpl、ハンドルクラス、エンベローブクラス、チェシャ猫クラス、とも言われる

単一コンポーネントラッパー

ファサードのテクニックの応用

マルチコンポーネントラッパー