コンテンツにスキップ

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_ptrboost::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

[編集]