More C++ Idioms/スマートポインタ(Smart Pointer)

出典: フリー教科書『ウィキブックス(Wikibooks)』
C++11以降 std::auto_ptr の使用は非推奨です。

std::auto_ptr が非推奨になった理由の1つは、その所有権の移譲方法が問題を引き起こす可能性があることです。std::auto_ptr は所有権を移譲するスマートポインタですが、その動作に問題がありました。例えば、コピーが行われると所有権が移動してしまい、元のポインタは無効になるという問題があります。

そのため、C++11以降では std::auto_ptr は非推奨とされ、代わりに以下のようなスマートポインタが推奨されています:

std::unique_ptr
C++11で導入された std::unique_ptr は、所有権が唯一のポインタに移譲され、所有権の安全な移動を保証します。コピーが禁止されており、所有権の移譲が明確に制御されます。
std::shared_ptr
複数のポインタが同じリソースを共有できるようにするために使用されます。リソースが最後の std::shared_ptr が破棄されるまで有効に保持されます。
std::weak_ptr
std::shared_ptr から派生した弱参照ポインタであり、循環参照を防ぐのに役立ちます。リソースへのアクセスが必要な場合は、std::weak_ptrstd::shared_ptr にロックすることで行います。

これらの代替え技術は、std::auto_ptr の問題を解決し、より安全で柔軟な所有権の管理を提供します。特に、std::unique_ptr は所有権の移譲を明確にし、リソースの所有権を追跡するための強力な手段を提供します。そのため、std::auto_ptr の使用は避けるべきであり、新しいコードでは std::unique_ptr を使用することが推奨されます。

スマートポインタ(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 (完全にポインタのような意味論)
    • ※C++11以降は非推奨。std::unique_ptr/std::shared_ptr/std::weak_ptrを使用することが推奨となっている
  • boost::shared_ptr (完全にポインタのような意味論)
  • C++ での CORBA Var 型 (TAO (The ACE ORB) での TAO_Seq_Var_Base_T< T > クラス - あまりポインタのような意味論を持たない)

関連するイディオム[編集]

References[編集]