More C++ Idioms/スマートポインタ(Smart Pointer)
目次 |
スマートポインタ(Smart Pointer) [編集]
意図 [編集]
ハンドル・ボディ(Handle Body)イディオムか、封筒・便箋(Envelope Letter)イディオムの使用時における、 ボディクラスのシグネチャ変更に対するハンドルクラスの重複した変更の負担を軽減する。
別名 [編集]
- En Masse(全体) 委譲
動機 [編集]
ハンドル・ボディ(Handle Body)イディオムを使う場合、本体(ボディ)クラスのインタフェースをハンドルクラスにも重複して持たせる必要があるかもしれない。 利用者のコードで使用されるのはハンドルクラスだからである。 この重複は、しばしば退屈で間違いを起こしやすいものとなる。 スマートポインタ(Smart Pointer)イディオムは、この負担を軽減するために使われる。 スマートポインタ(Smart Pointer)イディオムは、しばしば、参照回数計測や自動的な所有権管理など、ハンドルクラス中にある種のスマートさ(賢さ)を持たせて使われる。
解法とサンプルコード [編集]
ハンドルクラスでオーバーロードされた矢印演算子を定義する。
class Body; class Handle // 完全にポインタのような意味論を提供する { public: void set (Body *b) { body_ = b; } Body * operator -> () const throw() { return body_; } Body & operator * () const throw () { return *body_; } private: mutable Body *body_; }; int main (void) { Handle h; h.set(new Body()); h->foo(); // Body::foo()を呼び出す一つの方法 (*h).foo(); // Body::foo()を呼び出す別の方法 }
-> 演算子の使用だけで、本体(ボディ)クラスのインタフェースの、ハンドルクラス内での重複を緩和できる。 別の方法は、上記コード断片のように逆参照演算子(*)をオーバーロードすることである。しかしこれは先の例(->演算子)ほど自然ではない。 ハンドルの抽象化が、ある種のポインタの抽象化であるならば、両方のオーバーロード演算子を提供するべきである(例:std::auto_ptr、boost::shared_ptr)。 ハンドルの抽象化がポインタのような抽象化でない場合には、* 演算子は提供しなくてもよい。 その代わり、const でオーバーロードされた矢印演算子(群)を提供すると有用かもしれない。 なぜなら、クライアントはいつでもハンドルクラスのオブジェクトとやりとりするからである。 クライアントのコードにとっては、ハンドルがオブジェクトであり、従って、 ハンドルの const 性は、それが適当であるならばいつでも、対応する本体(ボディ)に伝播されるべきである。 一般的に、const なハンドルから 非 const な本体(ボディ)オブジェクトを変更できるというわかりにくい挙動は避けるべきである。 純粋なポインタの意味論と異なり、ハンドルクラスから本体(ボディ)クラスへの自動的な型変換が望ましい場合もある。
class Body; class Handle // あまりポインタのような意味論を持たない { public: void set (Body *b) { body_ = b; } Body * operator -> () throw() { return body_; } Body const * operator -> () const throw() { return body_; } operator const Body & () const // 型変換 { return *body_; } operator Body & () // 型変換 { return *body_; } // operator *() は無し private: mutable Body *body_; }; int main (void) { Handle const h; h.set(new Body()); h->foo(); // Body::foo() が const な関数である場合のみコンパイルされる。 }
メンバ関数による変換関数とは別の方法として、 下記のように非メンバ get(Non-member get)イディオムを使う方法がある。 オーバーロードされた非メンバ get() 関数は、インタフェース原則(Interface Principle)に従って、ハンドルクラスと同じ名前空間内になければならない。
namespace H { class Body; class Handle { ... }; // 上記と同様 Body const & get (Handle const &h) { return *h.body_; } Body & get (Handle &h) { return *h.body_; } } // 名前空間 H の終了 int main (void) { H::Handle const h; h.set(new Body()); get(h).foo(); // Body::foo() が const な関数である場合のみコンパイルされる }
既知の利用 [編集]
- std::auto_ptr (完全にポインタのような意味論)
- boost::shared_ptr (完全にポインタのような意味論)
- C++ での CORBA Var 型 (TAO (The ACE ORB) での TAO_Seq_Var_Base_T< T > クラス - あまりポインタのような意味論を持たない)