More C++ Idioms/内部クラス(Inner Class)

内部クラス(Inner Class)
編集

意図 編集

  • 多重継承なしに複数のインタフェースを実装し、さらに自然に見えるアップキャストを提供する。
  • 単一の抽象化で同一インタフェースに対する複数の実装を提供する。

別名 編集

動機 編集

2つの独立したクラスライブラリによって提供される、 2つの独立したインタフェース中の仮想関数のシグネチャが 衝突する可能性がある。 特に、単一のクラスにおいてその衝突した関数を、 対象とするインタフェース毎に異なる方法で実装する必要がある場合、問題になる。 例えば、

class Base1 /// Moon によって提供
{
  public:
      virtual int open (int) = 0;
      /* virtual */ ~Base1() {}  // 多態的な delete が禁止されている
};

class Base2 /// Jupitor によって提供
{
  public:
      virtual int open (int) = 0;
      /* virtual */ ~Base2() {}  // 多態的な delete が禁止されている
};

class Derived : public Base1, public Base2
{
  public:
    virtual int open (int i)
    {
      // どちらの基本クラスからの呼び出し?
      return 0;
    }
    /* virtual */ ~Derived () {}
};

内部クラス(Inner Class)イディオムによってこの問題を解決できる。

解法とサンプルコード 編集

インタフェースクラス、ここでは Base1 と Base2 を変更せずに、Derived クラスを以下のように実装できる。

class Derived // 継承していないことに注意
{
  class Base1_Impl;
  friend class Base1_Impl;
  class Base1_Impl: public Base1 // public 継承であることに注意
  {
     public:
       Base1_Impl (Derived * p) : parent_ (p) {}
       virtual int open (int) 
       {
          return parent_->base1_open ();
       }
     private:
       Derived * parent_;
  } base1_obj;   // このメンバオブジェクトに注目

  class Base2_Impl;
  friend class Base2_Impl;
  class Base2_Impl: public Base2 // public 継承であることに注意
  {
     public:
       Base2_Impl (Derived * p) : parent_ (p) {}
       virtual int open (int) 
       {
          return parent_->base2_open ();
       }
     private:
       Derived * parent_;
  } base2_obj; // このメンバオブジェクトに注目

  int base1_open () {}
  int base2_open () {}

  public:
   
    Derived () : base1_obj (this), base2_obj(this) {}

    operator Base1 & () { return base1_obj; }
    operator Base2 & () { return base2_obj; }
};

int base1_open (Base1 & b1)
{
  return b1.open (1);
}

int base2_open (Base2 & b2)
{
  return b2.open (2);
}

int main (void)
{
  Derived d;
  base1_open (d);  // 継承におけるアップキャスト相当
  base2_open (d);  // 継承におけるアップキャスト相当
}

Derived クラス中の変換演算子の使い方に注意すること(Derived クラスは実際には派生クラスではない!)。 変換演算子によって、継承関係を持たなくとも Derived の Base1 への変換が可能となる。 メンバオブジェクト base1_obj と base2_obj を用いることでオブジェクトの生存期間に関する問題を解消している。 Derived オブジェクトとメンバオブジェクトの生存期間は同じである。

既知の利用 編集

Related Idioms 編集

References 編集

Thinking in C++ Vol 2 - Practical Programming --- by Bruce Eckel.