スマート ポインターとは何ですか?いつ使用する必要がありますか?
-
01-07-2019 - |
質問
スマート ポインターとは何ですか?いつ使用する必要がありますか?
解決
アップデート
この答えはかなり古いものであるため、当時「良かった」もの、つまり Boost ライブラリによって提供されたスマート ポインターについて説明しています。C++11 以降、標準ライブラリは十分なスマート ポインター型を提供しているため、 std::unique_ptr
, std::shared_ptr
そして std::weak_ptr
.
もあります std::auto_ptr
. 。これはスコープ付きポインタによく似ていますが、コピーされる「特別な」危険な機能も備えている点が異なります。これにより、予期せず所有権も譲渡されます。 最新の標準では非推奨となっているため、使用しないでください。使用 std::unique_ptr
その代わり。
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++ ポインタをラップするクラスで、ポイントされているオブジェクトの有効期間を管理します。単一のスマート ポインター タイプはありませんが、それらはすべて実際的な方法で生のポインターを抽象化しようとします。
スマート ポインターは、生のポインターよりも優先されるべきです。ポインターを使用する必要があると思われる場合 (まず、ポインターを使用する必要があるかどうかを検討してください) 本当に do) の場合、通常はスマート ポインターを使用することをお勧めします。スマート ポインターを使用すると、主にオブジェクトの削除忘れやメモリ リークなど、生のポインターに関する問題の多くが軽減されます。
生のポインタを使用する場合、プログラマは、オブジェクトが役に立たなくなったときに、オブジェクトを明示的に破棄する必要があります。
// 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.
参照カウント ポインターは、オブジェクトの有効期間がはるかに複雑で、コードの特定のセクションや別のオブジェクトに直接関連付けられていない場合に非常に役立ちます。
カウントされたポインターの参照には欠点が 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 の両方で weak_ptr
への弱い (カウントされない) 参照を定義します。 shared_ptr
.
他のヒント
最近の最新の C++ に対する簡単な答えは次のとおりです。
- スマートポインターとは何ですか?
これは値をポインターのように使用できる型ですが、自動メモリ管理の追加機能を提供します。スマート ポインタが使用されなくなると、スマート ポインタが指すメモリの割り当てが解除されます (「スマート ポインタ」も参照)。 詳しい定義はWikipediaにあります). - いつ使用すればよいですか?
メモリの所有権の追跡、割り当てまたは割り当て解除を伴うコード内。多くの場合、スマート ポインターを使用すると、これらの作業を明示的に行う必要がなくなります。 - しかし、どのような場合にどのスマート ポインタを使用すればよいのでしょうか?
- 使用
std::unique_ptr
同じオブジェクトへの複数の参照を保持するつもりがない場合。たとえば、スコープに入ると割り当てられ、スコープから出ると割り当てが解除されるメモリへのポインタとして使用します。 - 使用
std::shared_ptr
複数の場所からオブジェクトを参照したい場合、およびこれらの参照自体がすべてなくなるまでオブジェクトの割り当てを解除したくない場合。 - 使用
std::weak_ptr
複数の場所からオブジェクトを参照したい場合 - 無視して割り当てを解除しても問題ない参照の場合 (そのため、逆参照しようとするとオブジェクトがなくなったことに気づくだけです)。 - を使用しないでください。
boost::
スマートポインターまたはstd::auto_ptr
特別な場合を除いて、必要に応じて読んでください。
- 使用
- おい、どれを使うか聞いてないよ!
ああ、でもあなたは本当に認めたかったのです。 - では、どのような場合に通常のポインタを使用すればよいのでしょうか?
ほとんどの場合、メモリの所有権を意識しないコード内にあります。これは通常、他の場所からポインタを取得し、割り当ても割り当て解除も行わず、実行後も存続するポインタのコピーを保存しない関数内にあります。
スマートポインター いくつかの追加機能を備えたポインタのような型です。自動メモリ解放、参照カウントなど。
簡単な紹介がページにあります スマート ポインター - 何を、なぜ、どれを?.
シンプルなスマート ポインター タイプの 1 つは次のとおりです。 std::auto_ptr
(C++ 標準の第 20.4.5 章) これにより、スコープ外の場合にメモリの割り当てを自動的に解除でき、例外がスローされた場合に単純なポインタを使用するよりも堅牢ですが、柔軟性は低くなります。
もう一つの便利なタイプは、 boost::shared_ptr
これは参照カウントを実装し、オブジェクトへの参照が残っていない場合は自動的にメモリの割り当てを解除します。これはメモリ リークを回避するのに役立ち、実装が簡単です。 ライ.
このテーマは本で詳しく取り上げられています 「C++ テンプレート:ザ・コンプリートガイド』 デビッド・ヴァンデヴォールデ著、ニコライ・M.ジョスティス, 、第20章。スマートポインター。いくつかのトピックが取り上げられました:
- 例外からの保護
- ホルダー(注、 std::auto_ptr このようなタイプのスマート ポインタの実装です)
- リソースの取得は初期化です (これは、C++ での例外安全なリソース管理によく使用されます)
- ホルダーの制限
- 参照カウント
- 同時カウンターアクセス
- 破棄と割り当て解除
Chris、Sergdev、Llyod によって提供された定義は正しいです。ただし、生活をシンプルにするために、私はもっと単純な定義を好みます。スマート ポインターは、単純にオーバーロードするクラスです。 ->
そして *
オペレーター。つまり、オブジェクトは意味的にはポインタのように見えますが、参照カウントや自動破棄など、より優れた処理を行うことができます。shared_ptr
そして auto_ptr
ほとんどの場合はこれで十分ですが、独自の小さな特異性がいくつかあります。
スマート ポインタは、「char*」などの通常の (型指定された) ポインタに似ていますが、ポインタ自体がスコープ外になると、ポインタが指すものも削除される点が異なります。「->」を使用することで通常のポインターと同じように使用できますが、データへの実際のポインターが必要な場合は使用できません。そのためには、「&*ptr」を使用できます。
これは次の場合に役立ちます。
new で割り当てる必要があるが、そのスタック上の何かと同じ存続期間を持たせたいオブジェクト。オブジェクトがスマート ポインターに割り当てられている場合、プログラムがその関数/ブロックを終了すると、オブジェクトは削除されます。
クラスのデータ メンバー。オブジェクトが削除されると、デストラクターに特別なコードを追加せずに、所有するすべてのデータも削除されます (デストラクターが仮想であることを確認する必要がありますが、これはほとんどの場合良いことです)。 。
してもいいです ない 次の場合にスマート ポインターを使用したい場合:
- ...ポインタは実際にはデータを所有すべきではありません...つまり、データを使用しているだけで、それを参照している関数でもデータを存続させたい場合です。
- ...スマート ポインター自体は、ある時点で破壊されることはありません。絶対に破棄されないメモリ (動的に割り当てられるが明示的に削除されないオブジェクトなど) に保持されることは望ましくありません。
- ...2 つのスマート ポインターが同じデータを指す場合があります。(ただし、これを処理するさらに賢いポインターもあります...それは呼ばれます 参照カウント.)
以下も参照してください。
- ガベージコレクション.
- このスタック オーバーフローの質問 データの所有権について
ほとんどの種類のスマート ポインターは、ポインター先オブジェクトの破棄を処理します。オブジェクトを手動で破棄することを考える必要がなくなったので、非常に便利です。
最も一般的に使用されるスマート ポインターは次のとおりです。 std::tr1::shared_ptr
(または boost::shared_ptr
)、そしてあまり一般的ではありませんが、 std::auto_ptr
. 。定期的に使用することをお勧めします shared_ptr
.
shared_ptr
非常に多用途であり、オブジェクトを「DLL 境界を越えて渡す」必要があるケース (異なる場合によくある悪夢のようなケース) を含む、多種多様な破棄シナリオに対応します。 libc
はコードと DLL の間で使用されます)。
スマート ポインタは、ポインタのように機能するオブジェクトですが、さらに、構築、破壊、コピー、移動、参照解除の制御も提供します。
独自のスマート ポインターを実装することもできますが、多くのライブラリでもスマート ポインターの実装が提供されており、それぞれに異なる利点と欠点があります。
例えば、 ブースト は、次のスマート ポインタ実装を提供します。
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
.
これらはそれぞれの単なる線形説明であり、必要に応じて使用できます。詳細と例については、Boost のドキュメントを参照してください。
さらに、C++ 標準ライブラリは 3 つのスマート ポインターを提供します。 std::unique_ptr
独自の所有権のために、 std::shared_ptr
共有所有権と std::weak_ptr
. std::auto_ptr
C++03 には存在していましたが、現在は非推奨になっています。
同様の回答へのリンクは次のとおりです。 http://sickprogrammersarea.blogspot.in/2014/03/technical-interview-questions-on-c_6.html
スマート ポインタは、通常のポインタと同じように動作、見た目、操作感を持ちながら、より多くの機能を提供するオブジェクトです。C++ では、スマート ポインターは、ポインターをカプセル化し、標準のポインター演算子をオーバーライドするテンプレート クラスとして実装されます。これらには、通常のポインターに比べて多くの利点があります。これらは、null ポインターまたはヒープ オブジェクトへのポインターとして初期化されることが保証されています。Null ポインターを介した間接指定がチェックされます。削除する必要はありません。オブジェクトへの最後のポインタがなくなると、オブジェクトは自動的に解放されます。これらのスマート ポインターの重大な問題の 1 つは、通常のポインターとは異なり、継承が考慮されていないことです。スマート ポインターはポリモーフィック コードにとって魅力的ではありません。以下にスマート ポインターの実装例を示します。
例:
template <class X>
class smart_pointer
{
public:
smart_pointer(); // makes a null pointer
smart_pointer(const X& x) // makes pointer to copy of x
X& operator *( );
const X& operator*( ) const;
X* operator->() const;
smart_pointer(const smart_pointer <X> &);
const smart_pointer <X> & operator =(const smart_pointer<X>&);
~smart_pointer();
private:
//...
};
このクラスは、X 型のオブジェクトへのスマート ポインターを実装します。オブジェクト自体はヒープ上にあります。使用方法は次のとおりです。
smart_pointer <employee> p= employee("Harris",1333);
他のオーバーロードされた演算子と同様に、p は通常のポインタのように動作します。
cout<<*p;
p->raise_salary(0.5);
http://en.wikipedia.org/wiki/Smart_pointer
コンピューターサイエンスでは、スマートポインターは、自動ガベージコレクションや境界チェックなどの追加機能を提供しながらポインターをシミュレートする抽象データ型です。これらの追加機能は、効率を維持しながらポインターの誤用によって引き起こされるバグを減らすことを目的としています。スマートポインターは通常、メモリ管理の目的でそれらを指すオブジェクトを追跡します。ポインターの誤用は、バグの主要な原因です。ポインターを使用して記述されたプログラムによって実行されなければならない一定の割り当て、取引、および参照は、いくつかのメモリリークが発生する可能性が非常に高くなります。スマートポインターは、リソースの取引ロケーションを自動的にすることにより、メモリリークを防止しようとします。たとえば、オブジェクトへのポインター(または一連のポインターの最後のポインター)が破壊されると、範囲がなくなるため、先の尖ったオブジェクトも破壊されます。
C ++のこのチュートリアルポインターのクラスとすることを、3つのタイプに分割できます。
1) 生のポインタ :
T a;
T * _ptr = &a;
これらは、メモリ内の特定の場所へのメモリ アドレスを保持します。プログラムは複雑になり追跡するのが困難になるため、使用には注意してください。
const データまたはアドレスを持つポインター { 逆方向に読み取る }
T a ;
const T * ptr1 = &a ;
T const * ptr1 = &a ;
const であるデータ型 T へのポインタ。つまり、ポインターを使用してデータ型を変更することはできません。つまり *ptr1 = 19
;動作しないでしょう。ただし、ポインターを移動することはできます。つまり ptr1++ , ptr1--
;などが機能します。逆から読む:const である型 T へのポインタ
T * const ptr2 ;
データ型 T への const ポインター。つまり、ポインタを移動することはできませんが、ポインタが指す値を変更することはできます。つまり *ptr2 = 19
動作しますが、 ptr2++ ; ptr2--
などが機能しません。逆から読む:T 型への const ポインタ
const T * const ptr3 ;
const データ型 T への const ポインター。つまり、ポインターを移動したり、データ型ポインターをポインターに変更したりすることはできません。つまり 。 ptr3-- ; ptr3++ ; *ptr3 = 19;
動作しないでしょう
3) スマートポインター : { #include <memory>
}
シェアードポインタ:
T a ;
//shared_ptr<T> shptr(new T) ; not recommended but works
shared_ptr<T> shptr = make_shared<T>(); // faster + exception safe
std::cout << shptr.use_count() ; // 1 // gives the number of "
things " pointing to it.
T * temp = shptr.get(); // gives a pointer to object
// shared_pointer used like a regular pointer to call member functions
shptr->memFn();
(*shptr).memFn();
//
shptr.reset() ; // frees the object pointed to be the ptr
shptr = nullptr ; // frees the object
shptr = make_shared<T>() ; // frees the original object and points to new object
ポインタが指すオブジェクトを指す「モノ」の数を追跡するために、参照カウントを使用して実装されています。このカウントが 0 になると、オブジェクトは自動的に削除されます。つまり、オブジェクトを指すすべての share_ptr がスコープ外になると、objected が削除されます。これにより、new を使用して割り当てたオブジェクトを削除する必要があるという頭痛の種がなくなりました。
ウィークポインタ: 共有ポインターを使用するときに発生する環状参照に対処するのに役立ちます。2つの共有ポインターを指す2つのオブジェクトがあり、相互に共有ポインターを指す内部共有ポインターがある場合、周期的な参照があり、オブジェクトは削除されません。共有ポインターは範囲外になります。これを解決するには、内部メンバーをshared_ptrからweak_ptrに変更します。注記 :ウィーク ポインタが指す要素にアクセスするには、 lock() を使用します。これにより、weak_ptr が返されます。
T a ;
shared_ptr<T> shr = make_shared<T>() ;
weak_ptr<T> wk = shr ; // initialize a weak_ptr from a shared_ptr
wk.lock()->memFn() ; // use lock to get a shared_ptr
// ^^^ Can lead to exception if the shared ptr has gone out of scope
if(!wk.expired()) wk.lock()->memFn() ;
// Check if shared ptr has gone out of scope before access
見る : std::weak_ptr はどのような場合に役立ちますか?
ユニークなポインタ: 独占所有権を持つ軽量スマート ポインター。ポインター間でオブジェクトを共有せずに、ポインターが一意のオブジェクトを指す場合に使用します。
unique_ptr<T> uptr(new T);
uptr->memFn();
//T * ptr = uptr.release(); // uptr becomes null and object is pointed to by ptr
uptr.reset() ; // deletes the object pointed to by uptr
一意の ptr が指すオブジェクトを変更するには、移動セマンティクスを使用します。
unique_ptr<T> uptr1(new T);
unique_ptr<T> uptr2(new T);
uptr2 = std::move(uptr1);
// object pointed by uptr2 is deleted and
// object pointed by uptr1 is pointed to by uptr2
// uptr1 becomes null
参考文献:これらは本質的に const ポインター、つまり const であり、より適切な構文では移動できないポインターと考えることができます。
見る : C++ におけるポインター変数と参照変数の違いは何ですか?
r-value reference : reference to a temporary object
l-value reference : reference to an object whose address can be obtained
const reference : reference to a data type which is const and cannot be modified
参照 :https://www.youtube.com/channel/UCOGtxYTB6vo6MQ-WQ9W_nQ この質問を指摘してくれたアンドレに感謝します。
スマート ポインターはクラスであり、通常のポインターのラッパーです。通常のポインターとは異なり、スマート ポイントのライフ サークルは参照カウント (スマート ポインター オブジェクトが割り当てられた回数) に基づいています。したがって、スマート ポインターが別のスマート ポインターに割り当てられるたびに、内部参照カウントがプラスされます。そして、オブジェクトがスコープ外になるたびに、参照カウントからマイナスが減ります。
自動ポインターは似ていますが、スマート ポインターとはまったく異なります。自動ポインタオブジェクトが変数のスコープ外に出るたびにリソースの割り当てを解除する便利なクラスです。これにより、(動的に割り当てられたメモリへの) ポインターがある程度、スタック変数 (コンパイル時に静的に割り当てられる) と同様に機能します。
スマート ポインタは、メモリの割り当て解除、リソースの共有、転送について心配する必要がないものです。
これらのポインタは、Java での割り当てと同様の方法で使用できます。Java ではガベージ コレクターがそのトリックを実行しますが、スマート ポインターではこのトリックはデストラクターによって実行されます。
既存の回答は優れていますが、スマート ポインターが解決しようとしている問題に対する (完全な) 回答ではない場合の対処方法については説明していません。
とりわけ(他の回答でよく説明されています)スマートポインターを使用することは、次のことを解決する可能性があります。 抽象クラスを関数の戻り値の型として使用するにはどうすればよいでしょうか? この質問の重複としてマークされています。ただし、C++ で戻り値の型として抽象 (または実際には任意の) 基本クラスを指定したくなった場合に最初に尋ねる質問は、「実際にはどういう意味ですか?」ということです。C++ の慣用的なオブジェクト指向プログラミング (およびこれが他の言語とどのように異なるか) については、(さらなる参照を含む) 詳しい説明が、C++ のドキュメントにあります。 ブースト ポインタ コンテナ ライブラリ. 。要約すると、C++ では所有権について考える必要があります。どのスマート ポインタが役に立ちますが、唯一の解決策ではない、または常に完全な解決策 (ポリモーフィック コピーを提供しない) であり、必ずしもインターフェイスで公開したい解決策であるとは限りません (関数の戻り値はひどいものに聞こえます)インターフェースによく似ています)。たとえば、参照を返すだけで十分な場合があります。しかし、これらのすべてのケース (スマート ポインター、ポインター コンテナー、または単に参照を返す) では、オブジェクトからの戻り値を変更しています。 価値 何らかの形で 参照. 。本当にコピーが必要な場合は、定型句「イディオム」をさらに追加するか、C++ の慣用的 (またはその他の) OOP を超えて、次のようなライブラリを使用してより汎用的な多態性へ移行する必要があるかもしれません。 アドビポリ または Boost.TypeErasure.