スマート ポインターとは何ですか? また、いつ使用する必要がありますか?
14 に答える
アップデート
この回答はかなり古いため、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++ ポインターをラップするクラスです。単一のスマート ポインター型はありませんが、すべての型は生のポインターを実用的な方法で抽象化しようとします。
生のポインターよりもスマート ポインターを優先する必要があります。ポインターを使用する必要があると感じた場合 (最初に実際に使用するかどうかを検討してください)、通常はスマート ポインターを使用することをお勧めします。これにより、主にオブジェクトの削除を忘れたり、メモリ リークが発生したりする生のポインターに関する多くの問題を軽減できるからです。
生のポインターを使用すると、プログラマーはオブジェクトが不要になったときにオブジェクトを明示的に破棄する必要があります。
// 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
s は、オブジェクトの有効期間を特定のコード ブロックに結び付けたい場合、または別のオブジェクト内にメンバー データとして埋め込んだ場合、その別のオブジェクトの有効期間に役立ちます。オブジェクトは、含まれているコード ブロックが終了するまで、または含まれているオブジェクト自体が破棄されるまで存在します。
より複雑なスマート ポインター ポリシーには、ポインターの参照カウントが含まれます。これにより、ポインターをコピーできます。オブジェクトへの最後の「参照」が破棄されると、オブジェクトは削除されます。このポリシーは および によって実装され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.
参照カウント ポインターは、オブジェクトの有効期間がはるかに複雑で、コードの特定のセクションや別のオブジェクトに直接関連付けられていない場合に非常に役立ちます。
参照カウント ポインターには 1 つの欠点があります — ダングリング参照が作成される可能性です。
// 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!
もう 1 つの可能性は、循環参照を作成することです。
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 の両方で、 aweak_ptr
への弱い (カウントされない) 参照を定義する aが定義されていますshared_ptr
。
スマート ポインターは、自動メモリ割り当て解除、参照カウントなどの追加機能を備えたポインターのような型です。
スマートポインター- 何を、なぜ、どれを?.
単純なスマート ポインター型の 1 つstd::auto_ptr
(C++ 標準の 20.4.5 章) は、メモリがスコープ外になったときに自動的にメモリの割り当てを解除でき、例外がスローされたときに単純なポインターの使用よりも堅牢ですが、柔軟性は低くなります。
別の便利なタイプはboost::shared_ptr
、参照カウントを実装し、オブジェクトへの参照が残っていない場合にメモリの割り当てを自動的に解除するものです。これは、メモリ リークを回避するのに役立ち、RAIIの実装に簡単に使用できます。
この件については、David Vandevoorde 著、Nicolai M. Josuttis 著「C++ Templates: The Complete Guide」の第 20 章「スマート ポインタ」で詳しく説明されています。取り上げるいくつかのトピック:
- 例外からの保護
- ホルダー (注意、std::auto_ptrはそのようなタイプのスマート ポインターの実装です)
- リソースの取得は初期化です(これは、C++ での例外セーフなリソース管理によく使用されます)
- ホルダーの制限
- 参照カウント
- 同時カウンター アクセス
- 破壊と解放
Chris、Sergdev、Llyodによって提供された定義は正しいです。しかし、私の人生をシンプルに保つために、よりシンプルな定義を好みます。スマートポインターは、->
and*
演算子をオーバーロードするクラスです。つまり、オブジェクトは意味的にはポインターのように見えますが、参照カウントや自動破棄など、よりクールなことを実行でき
shared_ptr
、auto_ptr
ほとんどの場合は十分ですが、独自の小さな特異性のセットが付属しています。
スマート ポインターは、"char*" のような通常の (型指定された) ポインターに似ていますが、ポインター自体がスコープ外になると、それが指すものも削除されます。「->」を使用して、通常のポインターと同じように使用できますが、データへの実際のポインターが必要な場合は使用できません。そのためには、「&*ptr」を使用できます。
次の場合に役立ちます。
new で割り当てる必要があるが、そのスタック上の何かと同じ寿命を持ちたいオブジェクト。オブジェクトがスマート ポインターに割り当てられている場合、プログラムがその関数/ブロックを終了すると、それらは削除されます。
クラスのデータ メンバー。オブジェクトが削除されると、デストラクタに特別なコードがなくても、所有されているすべてのデータも削除されます (デストラクタが仮想であることを確認する必要があります。これは、ほとんどの場合、適切なことです)。 .
次の場合は、スマート ポインターを使用したくない場合があります。
- ...ポインタは実際にはデータを所有すべきではありません...つまり、データを使用しているだけで、参照している関数で存続させたい場合。
- ... スマート ポインター自体は、ある時点で破棄されることはありません。決して破棄されないメモリ (動的に割り当てられるが明示的に削除されないオブジェクトなど) に配置したくない場合。
- ... 2 つのスマート ポインターが同じデータを指している可能性があります。(ただし、それを処理するさらにスマートなポインターがあります...これは参照カウントと呼ばれます。)
以下も参照してください。
- ガベージコレクション。
- データの所有権に関するこのスタックオーバーフローの質問
スマート ポインターは、ポインターのように機能するオブジェクトですが、さらに、構築、破棄、コピー、移動、および逆参照を制御できます。
独自のスマート ポインターを実装することもできますが、多くのライブラリでは、それぞれに異なる長所と短所を持つスマート ポインターの実装も提供されています。
たとえば、Boostは次のスマート ポインターの実装を提供します。
shared_ptr<T>
T
オブジェクトがいつ不要になるかを判断するために参照カウントを使用するためのポインタです。scoped_ptr<T>
範囲外になると自動的に削除されるポインタです。割り当てはできません。intrusive_ptr<T>
別の参照カウント ポインターです。よりも優れたパフォーマンスを提供しますshared_ptr
が、型T
が独自の参照カウント メカニズムを提供する必要があります。weak_ptr<T>
shared_ptr
は弱いポインタであり、循環参照を回避するために と連携して動作します。shared_array<T>
は に似shared_ptr
ていますが、 の配列用ですT
。scoped_array<T>
は に似scoped_ptr
ていますが、 の配列用ですT
。
これらはそれぞれの 1 つの直線的な説明にすぎず、必要に応じて使用できます。詳細と例については、Boost のドキュメントを参照してください。
さらに、C++ 標準ライブラリは 3 つのスマート ポインターを提供します。std::unique_ptr
一意の所有権、std::shared_ptr
共有所有権、およびstd::weak_ptr
. std::auto_ptr
C++03 には存在していましたが、現在は非推奨です。
ほとんどの種類のスマート ポインターは、ポインターへのオブジェクトの破棄を処理します。手動でオブジェクトを破棄することを考える必要がなくなるため、非常に便利です。
最も一般的に使用されるスマート ポインターはstd::tr1::shared_ptr
(またはboost::shared_ptr
) で、あまり一般的ではありませんが、std::auto_ptr
. の定期的な使用をお勧めしますshared_ptr
。
shared_ptr
libc
は非常に用途が広く、オブジェクトを「DLL の境界を越えて渡す」必要がある場合 (コードと DLL の間で異なる が使用されている場合によくある悪夢のようなケース) など、さまざまな廃棄シナリオに対応します。
http://en.wikipedia.org/wiki/Smart_pointer
コンピューター サイエンスでは、スマート ポインターは、自動ガベージ コレクションや境界チェックなどの追加機能を提供しながら、ポインターをシミュレートする抽象データ型です。これらの追加機能は、効率を維持しながらポインターの誤用によって引き起こされるバグを減らすことを目的としています。通常、スマート ポインターは、メモリ管理の目的で、スマート ポインターを指すオブジェクトを追跡します。ポインタの誤用は、バグの主な原因です。ポインタを使用して記述されたプログラムで実行する必要がある定数の割り当て、割り当て解除、および参照により、何らかのメモリ リークが発生する可能性が非常に高くなります。スマート ポインターは、リソースの解放を自動的に行うことでメモリ リークを防ごうとします。オブジェクトへのポインター (または一連のポインターの最後のポインター) が破棄されると、
スマート ポインターは、メモリの割り当て解除、リソースの共有、および転送について心配する必要がないものです。
Java で割り当てが機能するのと同じように、これらのポインターを非常にうまく使用できます。Java ではガベージ コレクターがトリックを行いますが、スマート ポインターではデストラクタがトリックを行います。
既存の回答は適切ですが、解決しようとしている問題に対するスマート ポインターが (完全な) 回答ではない場合の対処方法については説明していません。
とりわけ(他の回答でよく説明されています)スマートポインターを使用することは、抽象クラスを関数の戻り値の型としてどのように使用しますか?これは、この質問の複製としてマークされています。ただし、C++ で抽象 (または実際には任意の) 基本クラスを戻り値の型として指定したくなった場合に最初に尋ねる質問は、「本当はどういう意味ですか?」ということです。ブースト ポインター コンテナー ライブラリのドキュメントには、C++ での慣用的なオブジェクト指向プログラミング (およびこれが他の言語とどのように異なるか) に関する適切な議論 (さらなる参照を含む) があります。. 要約すると、C++ では所有権について考える必要があります。どのスマート ポインターが役立ちますが、唯一の解決策ではなく、常に完全な解決策 (ポリモーフィック コピーを提供しません) ではなく、常にインターフェイスで公開したい解決策ではありません (関数の戻り値はひどい音に聞こえます)。インターフェイスによく似ています)。たとえば、参照を返すだけで十分な場合があります。しかし、これらすべてのケース (スマート ポインター、ポインター コンテナー、または単に参照を返す) では、戻り値を値から何らかの形の参照に変更しました。本当にコピーが必要な場合は、ボイラープレートの「イディオム」を追加するか、慣用的な (またはその他の) C++ の OOP を超えて、Adobe PolyやBoost.TypeErasureなどのライブラリを使用して、より一般的なポリモーフィズムに移行する必要があります。.