株式会社エス・スリー・フォー

Handle-Bodyイディオム : 参照カウントによる実装

オブジェクト(クラス)のメンバ変数や関数の引数に値を用いるか、あるいは参照(ポインタ)を用いるかはプログラマの頭を悩ませます。

一般には値の方が良い、とされています。なぜなら値は参照(ポインタ)よりシンプルだからです。

ポインタが一方から他方へ引き渡されたとき、一つの実体を複数の場所で共有することになります。このときプログラマは実体のライフタイム(その実体をどこで/いつdeleteするか)を厳密に管理しなければなりません。複雑に絡み合ったポインタを矛盾なく解きほぐすのは相当に神経を使う作業となります。

しかしながら一方で値には問題があります。ひとつには値のコピーはコスト高となることがあるのです。オブジェクト内部に数多くのメンバを内在するときなど、そのコピーに必要な時間と手間はポインタに較べて非常に大きくなります。また、値渡しとはコピーを作ってそれを他方に引き渡すことになります。引き渡された側ではその本体(コピー元)にアクセスすることができず、値渡しそのこと自体が誤りである場合も少なくありません。

Handle-Bodyは値と参照(ポインタ)の双方のメリットを活かすイディオム(定石)です。
Handle-Bodyイディオムによって、ポインタを値のように扱うことが可能となり、オブジェクトの共有やライフタイム管理、そしてコストのかからないコピーといったことが実現できます。

Handle-Bodyイディオム(Bridgeパターン)とは、インタフェースと実装とを分離するからくりです。

Handle(インタフェース)がBody(実装)をその内部に持ち、Handleのメソッド呼び出しはそのまま内包するBodyに送られます。
そしてこのとき、HandleがBodyをポインタとして内包するなら、Handleのメンバ変数はポインタひとつですから非常にコンパクトでコピーを非常に短く/速く行えます。

しかし、HandleがBodyの'単なるポインタ'をメンバとして持った場合、Handle間でのコピーはひとつの実体を複数のHandleが共有することとなり、冒頭で述べた共有された実体のライフタイム管理が複雑になります。

共有された実体のライフタイム管理問題の解決のため、'参照カウント'を導入しましょう。参照カウントとは、ひとつの実体に対していくつのオブジェクトが参照しているかを表す数です。参照カウントは、あるオブジェクトがその実体への参照を開始したとき+1され、その実体への参照の必要がなくなるとき(参照しているオブジェクトがデストラクトされるとき)に-1されます。
そして参照カウントが0となったとき、実体そのものをデストラクトするからくりを用意するのです。

参照カウントを介して実体にアクセスすれば、プログラマはもはや実体の破壊タイミングを気にする必要はなくなります。複数のオブジェクトがひとつの実体を参照しているとき、最後のひとつのオブジェクトが参照を必要としなくなった瞬間、実体が自動的に破壊されます。
参照カウントは極めてにシンプルなガベジ・コレクタとして機能します。

HandleがBodyの参照カウントを保持することで値と参照(ポインタ)の双方のメリットを利用できるわけです。

参照カウントによる共有ポインタの実装のひとつがBoostshared_ptrです。

shared_ptrはクラス・テンプレートであり、たとえば boost::shared_ptrはXへの共有ポインタとなります。

shared_ptr間のコピーは内包するポインタへの参照カウントを+1し、shared_ptrのデストラクタは参照カウント-1します。そして参照カウントが0となったとき、ポインタがdeleteされます。

{
  boost::shared_ptr<int> p0(new int(123)); // [*] 参照数:1
  {
    boost::shared_ptr<int> p1 = p0; // 参照数:2
    std::cout << *p1 << std::endl;
    // ここでp1が破壊され、参照数:1
  }
  // ここでp0が破壊され、参照数:0 -> [*] で作られたintが解放される。
}

boost::shared_ptrを利用してHandle-Bodyイディオムの単純な実装を試みました。

HandleとBodyの基底クラス

BodyBase.h

#ifndef BODYBASE_H
#define BODYBASE_H

#include <boost/utility.hpp> // noncopyable

/*
 * BodyBase
 */
class BodyBase : public boost::noncopyable {
public:
  virtual ‾BodyBase() {}
};

#endif

HandleBase.h

#ifndef HANDLEBASE_H
#define HANDLEBASE_H

#include <boost/shared_ptr.hpp> // shared_ptr
#include "BodyBase.h"

/*
 * HandleBase
 */
class HandleBase {
  boost::shared_ptr<BodyBase> body_;
protected:
  HandleBase() {}
  HandleBase(BodyBase* p) : body_(p) {}
  BodyBase& body() const { return *body_; }
};

#endif

実装したHandle-Bodyを利用したサンプルを示します

サンプル

GreeterImpl.h

#ifndef GREETERIMPL_H
#define GREETERIMPL_H

#include <iostream>

#include "Greeter.h"

// abstract
class GreeterImpl : public BodyBase {
public:
  virtual void greet() const =0;
};

// concrete
class GreeterJa : public GreeterImpl {
  virtual void greet() const;
  GreeterJa();
  ‾GreeterJa();
public:
  static Greeter make();
};

// concrete
class GreeterEn : public GreeterImpl {
  virtual void greet() const;
  GreeterEn();
  ‾GreeterEn();
public:
  static Greeter make();
};

#endif

Greeter.h

#ifndef GREETER_H
#define GREETER_H

#include "HandleBase.h"

class GreeterImpl;

class Greeter : public HandleBase {
  GreeterImpl& body() const;
public:
  Greeter() {}
  Greeter(const Greeter& other) : HandleBase(other) {}
  Greeter(GreeterImpl* impl);
  void greet();
};

#endif

GreeterImpl.cpp

#include "GreeterImpl.h"

void GreeterJa::greet() const 
{ std::cout << "こんにちは" << std::endl; }

GreeterJa::GreeterJa()  
{ std::cout << "おはようございます" << std::endl; }

GreeterJa::‾GreeterJa()
{ std::cout << "おやすみなさい" << std::endl; }

Greeter GreeterJa::make() 
{ return Greeter(new GreeterJa); }

void GreeterEn::greet() const 
{ std::cout << "Hello!" << std::endl; }

GreeterEn::GreeterEn()  
{ std::cout << "Good morning." << std::endl; }

GreeterEn::‾GreeterEn() 
{ std::cout << "Good night." << std::endl; }

Greeter GreeterEn::make() 
{ return Greeter(new GreeterEn); }

Greeter.cpp

#include "Greeter.h"
#include "GreeterImpl.h"

Greeter::Greeter(GreeterImpl* impl) : HandleBase(impl) {}

GreeterImpl& Greeter::body() const {
  return static_cast<GreeterImpl&>(HandleBase::body());
}

void Greeter::greet() {
  body().greet();
}

trial.cpp

#include <iostream>

#include "GreeterImpl.h"

/*
 * TRIAL
 */
int main() {
  Greeter g;

  {
  std::cout << "--- create GreeterJa" << std::endl;
  Greeter j = GreeterJa::make();
  g = j;
  }
  g.greet();

  {
  std::cout << "--- create GreeterEn" << std::endl;
  Greeter e = GreeterEn::make();
  g = e;
  }
  g.greet();

  return 0;
}

実行結果

--- create GreeterJa
おはようございます
こんにちは
--- create GreeterEn
Good morning.
おやすみなさい
Hello!
Good night.