More C++ Idioms/ポインタ参照前後での実行(Execute-Around Pointer)

ポインタ参照前後での実行(Execute-Around Pointer)

編集

意図

編集

あるオブジェクトに対する各関数呼び出し前後で、透過的に(全ての関数について同じ)何らかの動作を実行するスマートポインタオブジェクトを提供する。[1]

別名

編集

スマートポインタの二重適用

動機

編集

しばしば、あるクラスのメンバ関数呼び出しの度に、何らかの機能を実行する必要がある場合がある。 例えば、マルチスレッドアプリケーションでは、データ構造を変更する前にロックし、その後でロックを解除しなくてはならない。 データ構造の可視化アプリケーションでは、毎回の挿入・削除操作後のデータ構造のサイズに興味があるかもしれない。

using namespace std;
class Visualizer {
    std::vector <int> & vect;
  public:
    Visualizer (vector<int> &v) : vect(v) {}
    void data_changed () {
       std::cout << "現在のサイズ: " << vect.size();
    }
};
int main () // データ可視化アプリケーション
{
  std::vector <int> vector;
  Visualizer visu (vector);
  //...
  vector.push_back (10);
  visu.data_changed ();
  vector.push_back (20);
  visu.data_changed ();
  // もっとたくさんの insert/remove の呼び出しと
  // 対応する可視化コードの呼び出し
}

このような関数呼び出しの繰り返しはエラーを招きやすく退屈である。 自動的に可視化コードが呼び出されるのが理想だろう。 Visualizer は std::list<int> についても同様にも使われうるだろう。 このように、単一のクラスの一部分ではなく、むしろ複数のクラスに渡るものは、一般にアスペクト(aspect)として知られている。 このイディオムは、単純なアスペクトを設計し実装するのに有用である。

Solution and Sample Code

編集
class VisualizableVector {
  public:
    class proxy {
      public:
        proxy (vector<int> *v) : vect (v) {
          std::cout << "呼び出し前のサイズ: " << vect->size ();
        }
        vector<int> * operator -> () {
          return vect;
        }
        ~proxy () {
          std::cout << "呼び出し後のサイズ: " << vect->size ();
        }
      private:
        vector <int> * vect;
    };
    VisualizableVector (vector<int> *v) : vect(v) {}    
    proxy operator -> () {
       return proxy (vect);
    }
  private:
    vector <int> * vect;
};
int main()
{
  VisualizableVector vector (new vector<int>);
  //...
  vector->push_back (10); // . 演算子ではなく -> 演算子が使われていることに注意せよ
  vector->push_back (20);
}

VisualizableVector のオーバーロードされた -> 演算子は一時的な proxy オブジェクトを生成し、返す。 proxy オブジェクトのコンストラクタでは vector のサイズをログ出力する。 その後、proxy オブジェクトのオーバーロードされた -> 演算子が呼び出され、 ベースとしている vector オブジェクトに対する素のポインタを返すことによって、 単に vector オブジェクトに呼び出しを転送する。 vector への実際の呼び出しが終了した後、proxy のデストラクタが再びサイズをログ出力する。 このように、可視化用のログは透過的であり、main 関数はぐちゃぐちゃにならずに済む。 このイディオムは、より汎用的で強力な Execute Around Proxy idiom の特別な場合である。

賢くテンプレートと組み合わせ、オーバーロードされた -> 演算子を連鎖させた時に、 このイディオムの真の力が現れる。

template <class NextAspect, class Para>
class Aspect
{
  protected:
    Aspect (Para p): para_(p) {}
    Para  para_;
  public:
    NextAspect operator -> () 
    {
      return para_;
    }
};

template <class NextAspect, class Para>
struct Visualizing : Aspect <NextAspect, Para>
{
  public:
    Visualizing (Para p) 
       : Aspect <NextAspect, Para> (p) 
    {
	std::cout << "可視化アスペクト前" << std::endl;
    }
    ~Visualizing () 
    {
	std::cout << "可視化アスペクト後" << std::endl;
    }
};
template <class NextAspect, class Para>
struct Locking : Aspect <NextAspect, Para>
{
  public:
    Locking (Para p) 
       : Aspect <NextAspect, Para> (p) 
    {
		std::cout << "ロックアスペクト前" << std::endl;
    }
    ~Locking () 
    {
	std::cout << "ロックアスペクト後" << std::endl;
    }
};
template <class NextAspect, class Para>
struct Logging : Aspect <NextAspect, Para>
{
  public:
    Logging (Para p) 
        : Aspect <NextAspect, Para> (p) 
    {
		std::cout << "ログアスペクト前" << std::endl;
    }
    ~Logging () 
    {
	std::cout << "ログアスペクト後" << std::endl;
    }
};
template <class Aspect, class Para>
class AspectWeaver 
{
public:
    AspectWeaver (Para p) : para_(p) {}    
    Aspect operator -> () 
    {
       return Aspect (para_);
    }
private:
	Para para_;
};

#define AW1(T,U) AspectWeaver <T <U, U>, U >
#define AW2(T,U,V) AspectWeaver <T < U <V, V> , V>, V >
#define AW3(T,U,V,X) AspectWeaver <T < U <V <X, X>, X> , X>, X >

int main()
{ 
  AW3(Visualizing, Locking, Logging, vector <int> *) 
	  X (new vector<int>);
  //...
  X->push_back (10); // . 演算子ではなく -> 演算子が使われていることに注意せよ
  X->push_back (20);
  return 0;
}

既知の利用

編集

関連するイディオム

編集

スマートポインタ(Smart Pointer)

References

編集
  1. ^ Execute Around Sequences - Kevlin Henney