回答:
良い例はキャッシュです。
最近アクセスしたオブジェクトについては、それらをメモリに保持したいので、それらへの強力なポインタを保持します。定期的に、キャッシュをスキャンして、最近アクセスされていないオブジェクトを決定します。それらをメモリに保持する必要はないので、強いポインタを取り除くことができます。
しかし、そのオブジェクトが使用中で、他のコードがそのオブジェクトへの強力なポインタを保持している場合はどうでしょうか。キャッシュがオブジェクトへの唯一のポインターを取り除くと、それを再び見つけることはできません。そのため、キャッシュは、オブジェクトがメモリに残っている場合に見つける必要があるオブジェクトへの弱いポインタを保持します。
これは、ウィークポインタが行うこととまったく同じです。オブジェクトがまだ存在している場合は、オブジェクトを見つけることができますが、他に何も必要がない場合は、オブジェクトを保持しません。
std::weak_ptr
ぶら下がりポインタの問題を解決する非常に良い方法です。生のポインタを使用するだけでは、参照されたデータが割り当て解除されたかどうかを知ることは不可能です。代わりに、std::shared_ptr
データを管理させ、データのstd::weak_ptr
ユーザーに提供することにより、ユーザーは、expired()
またはを呼び出してデータの有効性を確認できます。lock()
ます。
すべてのインスタンスが削除される前に削除されないデータの所有権をstd::shared_ptr
すべてのstd::shared_ptr
インスタンスが共有するため、これを単独で行うことはできませんstd::shared_ptr
。以下は、ダングリングポインターを確認する方法の例ですlock()
。
#include <iostream>
#include <memory>
int main()
{
// OLD, problem with dangling pointer
// PROBLEM: ref will point to undefined data!
int* ptr = new int(10);
int* ref = ptr;
delete ptr;
// NEW
// SOLUTION: check expired() or lock() to determine if pointer is valid
// empty definition
std::shared_ptr<int> sptr;
// takes ownership of pointer
sptr.reset(new int);
*sptr = 10;
// get pointer to data without taking ownership
std::weak_ptr<int> weak1 = sptr;
// deletes managed object, acquires new pointer
sptr.reset(new int);
*sptr = 5;
// get pointer to new data without taking ownership
std::weak_ptr<int> weak2 = sptr;
// weak1 is expired!
if(auto tmp = weak1.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
}
std::weak_ptr::lock
std::shared_ptr
管理オブジェクトの所有権を共有する新しいを作成します。
別の答え、うまくいけばもっと簡単に。(仲間のグーグルのために)
とオブジェクトがあるTeam
としMember
ます。
明らかにそれは関係です:Team
オブジェクトはそのへのポインタを持ちMembers
ます。また、メンバーにはTeam
オブジェクトへのバックポインターもある可能性があります。
次に、依存サイクルがあります。を使用shared_ptr
すると、循環参照でお互いを参照するため、参照を放棄したときにオブジェクトが自動的に解放されなくなります。これはメモリリークです。
これは、を使用して解除しweak_ptr
ます。「所有者」は通常使用shared_ptr
し、「所有者」はweak_ptr
その親を使用し、その親へのアクセスが必要なときに一時的に変換しshared_ptr
ます。
弱いptrを保存します:
weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared
その後、必要に応じて使用します
shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
// yes, it may fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
shared_ptr
は所有権を共有することです。そのため、メモリを解放する特別な責任は誰にもありません。使用されなくなったときに自動的に解放されます。ループがない限り...複数のチームが同じプレーヤーを共有している場合があります(過去のチーム?)。チームオブジェクトがメンバーを「所有」している場合はshared_ptr
、最初にを使用する必要はありません。
shared_ptr
その「チームメンバー」から参照されているため、いつ破壊されますか?あなたが説明しているのはループがない場合です。
@jleahyによって私に与えられた1つの例を次に示します。タスクのコレクションがあり、非同期で実行され、によって管理されているとしstd::shared_ptr<Task>
ます。これらのタスクで定期的に何かを実行したい場合があるので、タイマーイベントがa std::vector<std::weak_ptr<Task>>
をトラバースし、タスクに実行する何かを与えることができます。ただし、同時に、タスクが不要になり、終了したとタスクが同時に判断した可能性があります。したがって、タイマーは、弱いポインターから共有ポインターを作成し、その共有ポインターを使用することにより、タスクがまだ生きているかどうかを確認できます(nullでない場合)。
これらは、非同期ハンドラーが呼び出されたときにターゲットオブジェクトがまだ存在することが保証されていない場合に、Boost.Asioで役立ちます。トリックはweak_ptr
、std::bind
またはラムダキャプチャを使用して、非同期ハンドラオブジェクトにをバインドすることです。
void MyClass::startTimer()
{
std::weak_ptr<MyClass> weak = shared_from_this();
timer_.async_wait( [weak](const boost::system::error_code& ec)
{
auto self = weak.lock();
if (self)
{
self->handleTimeout();
}
else
{
std::cout << "Target object no longer exists!\n";
}
} );
}
これはの変形であるself = shared_from_this()
保留中の非同期ハンドラがしますしばしばBoost.Asioの例で見られるイディオム、ないターゲットオブジェクトの寿命を延ばす、まだターゲットオブジェクトが削除された場合、まだ安全です。
this
self = shared_from_this()
ハンドラーが同じクラス内のメソッドを呼び出すときにイディオムを使用するときの習慣の力。
ポインターを使用するときは、使用可能なポインターのさまざまなタイプを理解し、それぞれを使用することが理にかなっていることが重要です。次の2つのカテゴリには、4種類のポインタがあります。
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
生のポインタ(「レガシーポインタ」または「Cポインタ」と呼ばれることもあります)は「ベアボーン」ポインタの動作を提供し、バグとメモリリークの一般的な原因です。生のポインタはリソースの所有権を追跡する手段を提供しないため、開発者はメモリリークが発生していないことを確認するために手動で「削除」を呼び出す必要があります。リソースが共有されている場合、オブジェクトがまだリソースを指しているかどうかを知るのは難しいため、これは困難になります。これらの理由により、生のポインターは一般に避け、範囲が限定されたコードのパフォーマンスが重要なセクションでのみ使用する必要があります。
一意のポインタは、リソースへの基になる生のポインタを「所有」する基本的なスマートポインタであり、一意のポインタを「所有」するオブジェクトがスコープ外になると、deleteを呼び出して割り当てられたメモリを解放します。「一意」という名前は、1つのオブジェクトのみが特定の時点で一意のポインタを「所有」できるという事実を指します。所有権はmoveコマンドを介して別のオブジェクトに転送できますが、一意のポインターをコピーまたは共有することはできません。これらの理由により、特定の時点で1つのオブジェクトのみがポインターを必要とする場合、一意のポインターはrawポインターの良い代替手段となります。これにより、所有者のオブジェクトのライフサイクルの最後にメモリを解放する必要がなくなります。
共有ポインターは、一意のポインターに似た別のタイプのスマートポインターですが、多くのオブジェクトが共有ポインターに対する所有権を持つことができます。一意のポインタと同様に、共有ポインタは、すべてのオブジェクトがリソースを指していると、割り当てられたメモリを解放します。これは、参照カウントと呼ばれる手法でこれを実現します。新しいオブジェクトが共有ポインタの所有権を取得するたびに、参照カウントが1ずつ増加します。同様に、オブジェクトがスコープから外れたり、リソースへの参照を停止したりすると、参照カウントが1つ減ります。参照カウントがゼロに達すると、割り当てられたメモリが解放されます。これらの理由により、共有ポインターは非常に強力なタイプのスマートポインターであり、複数のオブジェクトが同じリソースを指す必要がある場合に使用する必要があります。
最後に、弱いポインターは、リソースを直接指すのではなく、別のポインター(弱いまたは共有)を指す別のタイプのスマートポインターです。ウィークポインターはオブジェクトに直接アクセスできませんが、オブジェクトがまだ存在するかどうか、またはオブジェクトが期限切れかどうかを知ることができます。弱いポインターを一時的に共有ポインターに変換して、指し示されたオブジェクトにアクセスできます(オブジェクトがまだ存在する場合)。説明のために、次の例を検討してください。
この例では、会議Bへのポインタが弱いです。あなたは会議Bの「所有者」ではないため、会議なしで終了する可能性があり、確認しない限り、それが終了したかどうかわかりません。終了していない場合は、参加して参加できます。それ以外の場合は参加できません。これは、ミーティングAとミーティングBの両方の「所有者」になる(同時に両方に参加する)ため、ミーティングBへの共有ポインタを持つこととは異なります。
この例は、弱いポインターがどのように機能するかを示しており、オブジェクトが外部オブザーバーである必要があるが、所有権を共有する責任を望まない場合に役立ちます。これは、2つのオブジェクトがお互いを指す必要があるシナリオ(別名循環参照)で特に役立ちます。共有ポインターでは、他のオブジェクトから「強く」ポイントされているため、どちらのオブジェクトも解放できません。ポインターの1つがウィークポインターである場合、ウィークポインターを保持しているオブジェクトは、まだ存在していれば、必要に応じて他のオブジェクトにアクセスできます。
すでに述べた他の有効なユースケースstd::weak_ptr
とは別に、マルチスレッド環境での素晴らしいツールです。
std::shared_ptr
と組み合わせて、std::weak_ptr
ダングリングポインターに対して安全です- std::unique_ptr
生のポインターと組み合わせて反対std::weak_ptr::lock()
アトミック操作です(weak_ptrのスレッドセーフについても参照)ディレクトリ(〜10.000)のすべての画像を同時にメモリに(たとえば、サムネイルキャッシュとして)ロードするタスクを考えてみます。明らかに、これを行うための最良の方法は、イメージを処理および管理する制御スレッドと、イメージをロードする複数のワーカースレッドです。これは簡単な作業です。これは非常に単純化された実装です(join()
etcは省略され、実際の実装ではスレッドは異なる方法で処理される必要があります)。
// a simplified class to hold the thumbnail and data
struct ImageData {
std::string path;
std::unique_ptr<YourFavoriteImageLibData> image;
};
// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
for( auto& imageData : imagesToLoad )
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas =
splitImageDatas( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
しかし、たとえばユーザーが別のディレクトリを選択したために、イメージのロードを中断したい場合は、さらに複雑になります。または、マネージャーを破壊したい場合でも。
m_imageDatas
フィールドを変更する前に、スレッド通信が必要で、すべてのローダースレッドを停止する必要があります。それ以外の場合、ローダーは、古いイメージであっても、すべてのイメージが完了するまでロードを続けます。単純化された例では、それほど難しくはありませんが、実際の環境では、状況ははるかに複雑になる可能性があります。
スレッドはおそらく複数のマネージャーによって使用されるスレッドプールの一部であり、そのうちのいくつかは停止されており、一部は停止されていません。単純なパラメーターimagesToLoad
はロックされたキューであり、これらのマネージャーは、さまざまな制御スレッドからイメージ要求をプッシュします。読者が反対側でリクエストをポップします-任意の順序で。そのため、通信が困難になり、遅くなり、エラーが発生しやすくなります。このような場合に追加の通信を回避する非常にエレガントな方法はstd::shared_ptr
、と組み合わせて使用することstd::weak_ptr
です。
// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
for( auto& imageDataWeak : imagesToLoad ) {
std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
if( !imageData )
continue;
imageData->image = YourFavoriteImageLib::load( imageData->path );
}
}
// a simplified manager
class Manager {
std::vector<std::shared_ptr<ImageData>> m_imageDatas;
std::vector<std::unique_ptr<std::thread>> m_threads;
public:
void load( const std::string& folderPath ) {
std::vector<std::string> imagePaths = readFolder( folderPath );
m_imageDatas = createImageDatas( imagePaths );
const unsigned numThreads = std::thread::hardware_concurrency();
std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas =
splitImageDatasToWeak( m_imageDatas, numThreads );
for( auto& dataRangeToLoad : splitDatas )
m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
}
};
この実装は、最初の実装とほぼ同じくらい簡単で、追加のスレッド通信を必要とせず、実際の実装ではスレッドプール/キューの一部になる可能性があります。期限切れのイメージはスキップされ、期限切れでないイメージが処理されるため、通常の操作中にスレッドを停止する必要はありません。所有者のポインターが期限切れになっていないかどうかリーダーのfnがチェックするため、パスをいつでも安全に変更したり、マネージャーを破棄したりできます。
http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptrは、std :: shared_ptrによって管理されるオブジェクトへの非所有(「弱い」)参照を保持するスマートポインターです。参照されるオブジェクトにアクセスするには、std :: shared_ptrに変換する必要があります。
std :: weak_ptrは一時的な所有権をモデル化します。オブジェクトが存在する場合にのみアクセスする必要があり、他のユーザーがいつでも削除できる場合、std :: weak_ptrを使用してオブジェクトを追跡し、stdに変換します。 :shared_ptrは一時的な所有権を想定します。この時点で元のstd :: shared_ptrが破棄されると、オブジェクトのライフタイムは、一時的なstd :: shared_ptrも破棄されるまで延長されます。
さらに、std :: weak_ptrは、std :: shared_ptrの循環参照を解除するために使用されます。
共有ポインターの欠点があります:shared_pointerは親子サイクルの依存関係を処理できません。子クラスが親クラスのオブジェクトを使用する場合、同じファイルで、親クラスが共有ポインターを使用して子クラスのオブジェクトを使用するかどうかを意味します。共有ポインターがサイクル依存関係のシナリオでデストラクターを呼び出していない場合でも、共有ポインターはすべてのオブジェクトを破棄できません。基本的に共有ポインタは、参照カウントメカニズムをサポートしていません。
この欠点は、weak_pointerを使用して克服できます。
weak_ptr
プログラムロジックを変更せずに循環依存関係をドロップイン置換の方法で対処するにはどうすればよいshared_ptr
ですか?」:-)
オブジェクトを所有したくない場合:
例:
class A
{
shared_ptr<int> sPtr1;
weak_ptr<int> wPtr1;
}
上記のクラスでは、wPtr1はwPtr1が指すリソースを所有していません。リソースが削除された場合、wPtr1は期限切れになります。
循環依存を回避するには:
shard_ptr<A> <----| shared_ptr<B> <------
^ | ^ |
| | | |
| | | |
| | | |
| | | |
class A | class B |
| | | |
| ------------ |
| |
-------------------------------------
クラスBとAのshared_ptrを作成すると、両方のポインターのuse_countは2になります。
shared_ptrがスコープ外に出てもカウントは1のままなので、AおよびBオブジェクトは削除されません。
class B;
class A
{
shared_ptr<B> sP1; // use weak_ptr instead to avoid CD
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void setShared(shared_ptr<B>& p)
{
sP1 = p;
}
};
class B
{
shared_ptr<A> sP1;
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void setShared(shared_ptr<A>& p)
{
sP1 = p;
}
};
int main()
{
shared_ptr<A> aPtr(new A);
shared_ptr<B> bPtr(new B);
aPtr->setShared(bPtr);
bPtr->setShared(aPtr);
return 0;
}
出力:
A()
B()
出力からわかるように、AおよびBポインターは削除されないため、メモリリークが発生します。
このような問題を回避するには、クラスAでshared_ptrの代わりにweak_ptrを使用してください。
私はへstd::weak_ptr<T>
のハンドルstd::shared_ptr<T>
と見なします。それによりstd::shared_ptr<T>
、それがまだ存在する場合にを取得できますが、その寿命は延長されません。このような視点が役立つシナリオはいくつかあります。
// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;
// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.
struct Widget {
std::weak_ptr< Texture > texture_handle;
void render() {
if (auto texture = texture_handle.get(); texture) {
// do stuff with texture. Warning: `texture`
// is now extending the lifetime because it
// is a std::shared_ptr< Texture >.
} else {
// gracefully degrade; there's no texture.
}
}
};
別の重要なシナリオは、データ構造のサイクルを壊すことです。
// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > next;
std::shared_ptr< Node > prev;
};
// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
std::shared_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::shared_ptr< Node > next;
std::weak_ptr< Node > prev;
};
// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
std::weak_ptr< Node > parent;
std::shared_ptr< Node > left_child;
std::shared_ptr< Node > right_child;
};
Herb Sutterは、言語機能(この場合はスマートポインター)の最適な使用法を説明する優れた講演を行っており、デフォルトで漏れのないことを保証し ます(つまり、すべてが構造によって所定の位置にカチッとはまるため、ほとんどねじ込むことができません)。必見です。