<戻る

続・スマートポインタテンプレートクラス(完全透明性を目指せ!)


 スマートポインタテンプレートクラスでは、参照カウンタ方式によるオブジェクトの寿命管理を行うテンプレートを作成しました。ただ、このクラスは色々と不便な点があります。嫌なのがスマートポインタ自体を結構意識しなければならない点。生成や登録は仕方がないとして、アクセスする時などはもっとそれこそ「スマート」に出来るはず。また、スマートポインタテンプレートクラスの実装レベルではまだポインタ演算は行えません。よって配列のようなアクセスもダメ。キャストもダメ。結局、現行では「削除を気にしなくても良い」という機能しか与えられていないんです。そこで、この章では出来る限りスマートポインタを意識さっせず透明性が高くなるようスマートポインタを改良して行きます。
 この章での考察の結果出来上がった改訂版スマートポインタの完全コードはこちらです。


@ 何をするのかToDoリストで整理

 テスト主導型開発(TDD)という開発手法がありまして、その中ではすべきことをToDoリストと呼んで明確化しています。大変わかりやすいので、まず、改良スマートポインタにやってもらいたいことをToDoリストにして整理しましょう。

ToDoリスト
・ スマートポインタは、登録されたオブジェクトを誰も指していなかったら消す
・ スマートポインタは、登録したポインタのアクセス演算子「->」と「*」演算子が使える
・ スマートポインタは、登録したポインタを配列の要素へのポインタ変数とみなした「[]」演算子が使える
・ スマートポインタは、アップキャストをカバーする

これらを1つ1つ潰していきます。


A 登録されたオブジェクトを誰も指していなかったら消す

 これは既存の機能としてもう実装されています。よって、この部分のToDoは終わりです。



B 登録したオブジェクトにアクセスする「->」と「*」演算子が使える

 ポインタは、その先にあるオブジェクトにアクセスする方法を3つ持っています。メンバ選択演算子(アロー演算子)「->」と間接演算子「*」そして配列参照演算子「[]」です。スマートポインタの演算子を通して、それぞれの演算子をオーバーロードしていきます。まず、単一アクセスである前者2つについて考えていきましょう。尚、これら演算子の詳しい説明はこちら

 メンバ選択演算子「->」は後置演算子ですから引数はありません。

smart_ptr.h
T* operator ->()
{
   return Ptr;   // Ptrは登録されているT型のオブジェクトへのポインタ
}

こうすると、smart_ptr型のオブジェクトObjについて、「Obj->」とすることで、登録されているポインタに間接アクセスできます。Objはポインタでは無くても出来るのが面白いところです。

 間接演算子も同様に単項演算子ですから、引数はありません。

smart_ptr.h
T& operator *()
{
   return *Ptr;
}

戻り値にアドレス演算子&を渡して、リードーオンリーのアクセスにするのがポイントです。この辺があやふやな人もこちらをどうぞ。

 これら演算子を用いた場合のアクセス例を示します。

smart_ptr<CObject> m_spObj;

int Val1 = m_spObj->m_DefineVal;
int Val2 = (*m_spObj).m_DefineVal2;

m_spObjを登録されているCObject型のポインタに置き換えたかのように扱えます。



C 配列参照演算子[ ]を実装

 この演算子のオーバーロードも特段難しい点はありません。

smart_ptr.h
T& operator [](int n)
{
   return Ptr[n];
}


D アップキャストをサポートする

 スマートポインタの2つのオブジェクト間でアップキャストが出来るようにします。アップキャストとは、親クラスのポインタに子クラスのポインタを代入し、親クラスの振る舞いで子クラスを操作する事です。2種のオブジェクトを登録しているスマートポインタ同士は継承関係はまったくないため、通常は代入行為は出来ません。そこで、テンプレート関数の機能をうまく利用します。

 簡単な例で振る舞いを説明します。次のようなテンプレートクラスを定義します。

template <class T>
class Temp
{
private:
   T* m_Ptr;   // 登録するポインタ

public:
   Temp( T* ptr){ m_Ptr = ptr;}  // コンストラクタ
   T* GetPtr(){ return m_Ptr;}   // ポインタを取得

   // 明示的キャスト
   template<class T2> void operator=(Temp<T2> &tmp)
   {
      m_Ptr = tmp.GetPtr();
   }

   // 暗黙的キャスト
   template<class T2> Temp( Temp<T2> &tmp)
   {
      m_Ptr = tmp.GetPtr();
   }
}

太文字部分に注目です。明示的なキャスト、これは=演算子をオーバーロードします。注目は、これがテンプレート関数になっている点です。引数(右辺値)にはT2型のオブジェクトを保持しているTempテンプレートオブジェクトの参照になっています。そして、内部ではm_Ptrに引数のポインタをGetPtr関数で取り出して代入を試みています。つまり、見かけ上Tempテンプレート同士の代入に見えますが、実質は内部のポインタ同士の代入であり、コンパイラが通れば、それはアップキャスト可能である証拠となります。暗黙的キャストの方も同様です。

 実際にテストしてみます。

class Animal
{
public:
   string m_Me;
   Animal(){ m_Me = "動物です"; }
}:

class Human : public Animal
{
public:
   Human(){ m_Me = "人です"};
}; 

AnimalとHuman派生クラスを作り、上のテンプレートに登録します。

Temp<Animal> tpAnimal( new Animal);
Temo<Human> tpHuman( new Human);

tpAnimal = tpHuman;   // 可能!

明示的な代入は可能です!暗黙の代入はテスト関数で実験です。

void WhoAreYou(Temp<Animal> me)
{
   cout << me.GetPtr()->m_Me << endl;  // 出力
}

標準出力に引数の動物の正体を出力します。人オブジェクトを登録したTempクラスを渡してみます。

WhoAreYou(tpHuman);

すると、標準出力は見事に「人です」となります。暗黙の型キャストもこれでOK!

この例をうまく使って参照カウンタをテンプレートスマートポインタ内で調節すれば、アップキャストが普通のポインタのように扱えるスマートポインタの完成です!!

 改正版スマートポインタの完全ソースをこちらに公開します