1. ホーム
  2. c++

[解決済み] スマートポインターとは何ですか?

2022-03-18 01:53:18

質問

スマートポインターとはどのようなもので、どのような場合に使用するのですか?

どのように解決するのですか?

アップデイト

この回答はかなり古いもので、当時「良かった」もの、つまりBoostライブラリが提供するスマートポインタについて説明しています。C++11以降、標準ライブラリは十分なスマートポインタの型を提供しています。 std::unique_ptr , std::shared_ptr そして std::weak_ptr .

もありました。 std::auto_ptr . これは、スコープ付きポインタに非常によく似ていますが、コピーされるという "特別" 危険な能力も持っており、これは予期せず所有権を移してしまうということを除けば、非常によく似ています。
C++11で非推奨となり、C++17で削除された ということで、使うべきではありません。

std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership. 
                                 // p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.


旧アンサー

スマートポインターは、「生の」(または「裸の」)C++ポインターをラップするクラスで、指されているオブジェクトの寿命を管理するためのものです。スマートポインタの型は1つではありませんが、どれも生のポインタを実用的な方法で抽象化しようとするものです。

スマートポインタは生ポインタよりも優先されるべきです。もしポインターを使う必要があると感じたら、(最初に 本当に なぜなら、スマートポインタを使えば、生ポインタの問題点(主にオブジェクトの削除忘れやメモリリーク)を軽減できるからです。

生ポインタの場合、プログラマはオブジェクトが不要になったときに明示的に破棄しなければなりません。

// Need to create the object to achieve some goal
MyObject* ptr = new MyObject(); 
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?

それに比べてスマートポインターは、オブジェクトを破棄するときのポリシーを定義しています。オブジェクトを作成する必要はありますが、破棄することを気にする必要はありません。

SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.

// Destruction of the object happens, depending 
// on the policy the smart pointer class uses.

// Destruction would happen even if DoSomething() 
// raises an exception

最も単純なポリシーは、スマートポインタラッパーオブジェクトのスコープに関わるもので、例えば、以下のように実装されています。 boost::scoped_ptr または std::unique_ptr .

void f()
{
    {
       std::unique_ptr<MyObject> ptr(new MyObject());
       ptr->DoSomethingUseful();
    } // ptr goes out of scope -- 
      // the MyObject is automatically destroyed.

    // ptr->Oops(); // Compile error: "ptr" not defined
                    // since it is no longer in scope.
}

なお std::unique_ptr インスタンスをコピーすることはできません。これは、ポインタが何度も (不正に) 削除されるのを防ぐためです。しかし、呼び出す他の関数にその参照を渡すことはできます。

std::unique_ptr は、オブジェクトの寿命を特定のコードブロックに結びつけたい場合や、他のオブジェクトの中にメンバーデータとして埋め込んだ場合、その他のオブジェクトの寿命となる場合に便利です。オブジェクトは、コードのブロックが終了するまで、またはオブジェクトが破壊されるまで存在します。

より複雑なスマートポインタのポリシーとして、ポインタの参照カウントがあります。これはポインターをコピーすることを可能にします。オブジェクトへの最後の "参照" が破棄されると、オブジェクトは削除されます。このポリシーは次のように実装されています。 boost::shared_ptr std::shared_ptr .

void f()
{
    typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
    MyObjectPtr p1; // Empty

    {
        MyObjectPtr p2(new MyObject());
        // There is now one "reference" to the created object
        p1 = p2; // Copy the pointer.
        // There are now two references to the object.
    } // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero. 
  // The object is deleted.

オブジェクトの寿命がもっと複雑で、コードの特定のセクションや他のオブジェクトに直接結びついていない場合、参照カウントされたポインタが非常に便利です。

参照カウント式ポインターの欠点は、ダングリングリファレンスが発生する可能性があることです。

// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!

もう一つの可能性は、循環参照を作ることです。

struct Owner {
   std::shared_ptr<Owner> other;
};

std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1

// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!

この問題を回避するために、BoostとC++11の両方が、C++11で定義された weak_ptr への弱い(カウントされない)参照を定義するためのものです。 shared_ptr .