More C++ Idioms/代数的階層(Algebraic Hierarchy)

代数的階層(Algebraic Hierarchy)

編集

意図

編集

密接に関連した複数の代数的な抽象(数)を単一の汎用的な抽象に隠蔽し、汎用的なインタフェースを提供する。

別名

編集

動機

編集

Smalltalk のような純粋なオブジェクト指向言語では、変数は、オブジェクトへの実行時の束縛であり、ラベルのように働く。 変数をあるオブジェクトに束縛するということは、オブジェクトにラベルを貼り付けるようなものである。 これらの言語における代入は、ラベルをあるオブジェクトからはがして、別のオブジェクトに貼り付けることに例えられる。 一方、C/C++ では、変数は、オブジェクトに対するラベルではなく、アドレスやオフセットの別名である。代入はラベル付けし直すことではなく、古い内容を新しい内容で上書きすることを意味する。代数的階層(Algebraic Hierarchy)イディオムは、C++ でオブジェクトへの束縛を示す弱い変数をシミュレートするため委譲された継承(delegated polymorphism)を用いる。 代数的階層(Algebraic Hierarchy)イディオムはまた、その実装で封筒・便箋(Envelope Letter)イディオムを使用する。 以下のようなコードを書けるようにすることが、このイディオムの背景にある動機である。

Number n1 = Complex (1, 2); // 複素数用のラベル n1
Number n2 = Real (10); // 実数用のラベル n2
Number n3 = n1 + n2; // 加算の結果を n3 としてラベル付け
Number n2 = n3; // ラベル付けし直す

解法とサンプルコード

編集

以下が、代数的階層(Algebraic Hierarchy)イディオムの実装を示す完全なコードである。

#include <iostream>

using namespace std;

struct BaseConstructor { BaseConstructor(int=0) {} };

class RealNumber;
class Complex;
class Number;

class Number
{
    friend class RealNumber;
    friend class Complex;

  public:
    Number ();
    Number & operator = (const Number &n);
    Number (const Number &n);
    virtual ~Number();

    virtual Number operator + (Number const &n) const;
    void swap (Number &n) throw ();
    
    static Number makeReal (double r);
    static Number makeComplex (double rpart, double ipart);

  protected:
    Number (BaseConstructor);
  
  private:
    void redefine (Number *n);
    virtual Number complexAdd (Number const &n) const;
    virtual Number realAdd (Number const &n) const;
    
    Number *rep;
    short referenceCount;
};

class Complex : public Number
{
  friend class RealNumber;
  friend class Number;
  
  Complex (double d, double e);
  Complex (const Complex &c);
  virtual ~Complex ();
  
  virtual Number operator + (Number const &n) const;
  virtual Number realAdd (Number const &n) const;
  virtual Number complexAdd (Number const &n) const;

  double rpart, ipart;
};

class RealNumber : public Number
{
  friend class Complex;
  friend class Number;
  
  RealNumber (double r);
  RealNumber (const RealNumber &r);
  virtual ~RealNumber ();
  
  virtual Number operator + (Number const &n) const;
  virtual Number realAdd (Number const &n) const;
  virtual Number complexAdd (Number const &n) const;
    
  double val;
};

/// (封筒・便箋(Envelope Letter)イディオムにおける)便箋(letters)によってのみ使用される
Number::Number (BaseConstructor)
: rep (0),
  referenceCount (1)
{}

/// ユーザと静的ファクトリ関数によって使用される
Number::Number () 
  : rep (0),
    referenceCount (0)
{}

/// ユーザと静的ファクトリ関数によって使用される
Number::Number (const Number &n)
: rep (n.rep),
  referenceCount (0)
{
  cout << "Number::Number による Number の生成\n";
  if (n.rep)
    n.rep->referenceCount++;
}

Number Number::makeReal (double r)
{
  Number n;
  n.redefine (new RealNumber (r));
  return n;
}

Number Number::makeComplex (double rpart, double ipart)
{
  Number n;
  n.redefine (new Complex (rpart, ipart));
  return n;
}

Number::~Number()
{
  if (rep && --rep->referenceCount == 0)
    delete rep;
}

Number & Number::operator = (const Number &n)
{
  cout << "Number::operator= による Number の代入\n";
  Number temp (n);
  this->swap (temp);
  return *this;
}

void Number::swap (Number &n) throw ()
{
  std::swap (this->rep, n.rep);
}

Number Number::operator + (Number const &n) const
{
  return rep->operator + (n);
}

Number Number::complexAdd (Number const &n) const 
{
  return rep->complexAdd (n);
}

Number Number::realAdd (Number const &n) const
{
  return rep->realAdd (n);
}

void Number::redefine (Number *n)
{
  if (rep && --rep->referenceCount == 0)
    delete rep;
  rep = n;
}

Complex::Complex (double d, double e)
  : Number (BaseConstructor()),
    rpart (d),
    ipart (e)
{
  cout << "Complex の生成\n";
}

Complex::Complex (const Complex &c)
  : Number (BaseConstructor()),
    rpart (c.rpart),
    ipart (c.ipart)
{
  cout << "Complex::Complex による Complex の生成\n";
}

Complex::~Complex()
{
  cout << "Complex::~Complex() 内部\n";
}

Number Complex::operator + (Number const &n) const
{ 
  return n.complexAdd (*this); 
}

Number Complex::realAdd (Number const &n) const
{
  cout << "Complex::realAdd\n";
  RealNumber const *rn = dynamic_cast <RealNumber const *> (&n);
  return Number::makeComplex (this->rpart + rn->val, 
                              this->ipart);
}

Number Complex::complexAdd (Number const &n) const
{
  cout << "Complex::complexAdd\n";
  Complex const *cn = dynamic_cast <Complex const *> (&n);
  return Number::makeComplex (this->rpart + cn->rpart, 
                              this->ipart + cn->ipart);
}

RealNumber::RealNumber (double r)
  : Number (BaseConstructor()),
    val (r)
{
  cout << "RealNumber の生成\n";
}

RealNumber::RealNumber (const RealNumber &r)
  : Number (BaseConstructor()),
    val (r.val)
{
  cout << "RealNumber::RealNumber による RealNumber の生成\n";
}

RealNumber::~RealNumber()
{
  cout << "RealNumber::~RealNumber() 内部\n";
}

Number RealNumber::operator + (Number const &n) const
{ 
  return n.realAdd (*this); 
}

Number RealNumber::realAdd (Number const &n) const
{
  cout << "RealNumber::realAdd\n";
  RealNumber const *rn = dynamic_cast <RealNumber const *> (&n);
  return Number::makeReal (this->val + rn->val);
}

Number RealNumber::complexAdd (Number const &n) const
{
  cout << "RealNumber::complexAdd\n";
  Complex const *cn = dynamic_cast <Complex const *> (&n);
  return Number::makeComplex (this->val + cn->rpart, cn->ipart);
}
namespace std
{
template <>
void swap (Number & n1, Number & n2)
{
  n1.swap (n2);
}
}
int main (void)
{
  Number n1 = Number::makeComplex (1, 2);
  Number n2 = Number::makeReal (10);
  Number n3 = n1 + n2;
  cout << "終了\n";

  return 0;
}

既知の利用

編集

関連するイディオム

編集

References

編集

Advanced C++ Programming Styles and Idioms by James Coplien, Addison Wesley, 1992.