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