私はC++ 11のスマートポインターの研究を開始しましたが、std::weak_ptr
の有用な使用法は見当たりません。 std::weak_ptr
が役に立つ/必要なときに誰かに教えてもらえますか?
良い例はキャッシュです。
最近アクセスしたオブジェクトについては、それらをメモリに保持したいので、それらへの強力なポインタを保持します。定期的にキャッシュをスキャンし、最近アクセスされていないオブジェクトを判断します。それらをメモリに保持する必要はないので、強力なポインタを取り除きます。
しかし、そのオブジェクトが使用中であり、他のコードがそれへの強力なポインタを保持している場合はどうでしょうか?キャッシュがオブジェクトへの唯一のポインタを取り除くと、再び見つけることはできません。そのため、キャッシュは、オブジェクトがメモリ内に残っているかどうかを見つける必要があるオブジェクトへの弱いポインタを保持します。
これはまさに弱いポインターの動作です。オブジェクトがまだ存在している場合はオブジェクトを見つけることができますが、他に必要なものがない場合はオブジェクトを保持しません。
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";
}
別の答えは、できればもっと簡単です。 (グーグル仲間向け)
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 failed if parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
@jleahyから与えられた1つの例を次に示します。タスクのコレクションがあり、非同期で実行され、std::shared_ptr<Task>
によって管理されているとします。これらのタスクで定期的に何かをしたい場合があるので、タイマーイベントは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";
}
} );
}
これは、Boost.Asioの例でよく見られるself = shared_from_this()
イディオムの変形で、保留中の非同期ハンドラーがnotターゲットオブジェクトの寿命を延ばしますが、ターゲットオブジェクトが削除されても安全です。
weak_ptr
は、特にユニットテストで、オブジェクトの正しい削除を確認するのにも適しています。典型的な使用例は次のようになります。
std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());
ポインターを使用する場合、使用可能なさまざまなタイプのポインターを理解し、各ポインターを使用することが理にかなっていることが重要です。次の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ポインター」とも呼ばれる)は、「ベアボーン」ポインターの動作を提供し、バグやメモリリークの一般的な原因です。 Rawポインターはリソースの所有権を追跡する手段を提供しないため、開発者は手動で「delete」を呼び出してメモリリークが発生しないようにする必要があります。これは、リソースが共有されている場合、オブジェクトがまだリソースを指しているかどうかを知るのが難しいため、困難になります。これらの理由から、生のポインターは一般的に避け、限られた範囲のコードのパフォーマンスが重要なセクションでのみ使用する必要があります。
一意のポインターは、リソースへの基礎となる生のポインターを「所有」する基本的なスマートポインターであり、一意のポインターを「所有」するオブジェクトが範囲外になると、deleteを呼び出して割り当てられたメモリを解放します。 「一意」という名前は、特定の時点で一意のポインタを「所有」できるのは1つのオブジェクトのみであるという事実を指します。所有権は、移動コマンドを介して別のオブジェクトに転送できますが、一意のポインターをコピーまたは共有することはできません。これらの理由により、特定の時点で1つのオブジェクトのみがポインターを必要とする場合、一意のポインターは生のポインターに代わる優れた代替手段となります。これにより、開発者は所有オブジェクトのライフサイクルの最後にメモリを解放する必要がなくなります。
共有ポインターは、一意のポインターに似たスマートポインターの別のタイプですが、多くのオブジェクトが共有ポインターに対する所有権を持つことができます。一意のポインターと同様に、共有ポインターは、すべてのオブジェクトがリソースを指すと、割り当てられたメモリを解放します。これは、参照カウントと呼ばれる手法でこれを実現します。新しいオブジェクトが共有ポインタの所有権を取得するたびに、参照カウントが1ずつ増加します。同様に、オブジェクトがスコープ外に出たり、リソースを指すのを停止すると、参照カウントは1つ減ります。参照カウントがゼロに達すると、割り当てられたメモリが解放されます。これらの理由から、共有ポインターは非常に強力なタイプのスマートポインターであり、複数のオブジェクトが同じリソースを指す必要がある場合に使用する必要があります。
最後に、弱いポインターは別のタイプのスマートポインターであり、リソースを直接指すのではなく、別のポインター(弱いまたは共有)を指します。弱いポインターはオブジェクトに直接アクセスすることはできませんが、オブジェクトがまだ存在するか、期限切れになっているかを知ることができます。弱いポインタを一時的に共有ポインタに変換して、ポイント先のオブジェクトにアクセスできます(まだ存在している場合)。説明のために、次の例を検討してください。
この例では、会議Bへの弱いポインタがあります。会議Bの「所有者」ではないため、会議Bは自分なしで終了でき、チェックしない限り終了したかどうかはわかりません。終了していない場合は参加して参加できますが、終了していない場合は参加できません。これは、ミーティングAとミーティングBの両方の「所有者」になる(同時に両方に参加する)ため、ミーティングBへの共有ポインターを持つこととは異なります。
この例は、弱いポインターがどのように機能するかを示しており、オブジェクトが外部observerである必要があるが、所有権を共有する責任を望まない場合に役立ちます。これは、2つのオブジェクトが相互にポイントする必要があるシナリオ(循環参照)で特に役立ちます。共有ポインタを使用すると、他のオブジェクトによって「強く」指示されているため、どちらのオブジェクトも解放できません。ポインターの1つがウィークポインターである場合、ウィークポインターを保持しているオブジェクトは、まだ存在していれば、必要に応じて他のオブジェクトにアクセスできます。
既に述べた他の有効なユースケースとは別に、std::weak_ptr
はマルチスレッド環境で素晴らしいツールです。
std::shared_ptr
をstd::weak_ptr
と組み合わせて使用すると、ぶら下がりのポインターに対して安全です-生のポインターと組み合わせてstd::unique_ptr
を使用した場合std::weak_ptr::lock()
はアトミック操作です( weak_ptrのスレッドセーフについて も参照)ディレクトリのすべての画像(〜10.000)を同時にメモリに読み込むタスクを検討します(たとえば、サムネイルキャッシュとして)。明らかにこれを行う最良の方法は、画像を処理および管理する制御スレッドと、画像をロードする複数のワーカースレッドです。これは簡単な作業です。これは非常に単純化された実装です(join()
などは省略され、実際の実装などではスレッドを異なる方法で処理する必要があります)
// 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がチェックするため、常に安全にパスを変更したり、マネージャーを破壊したりできます。
オブジェクトを所有したくない場合:
例:
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がodスコープを超えても、カウントは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を使用します。
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を使用して克服できます。
std::weak_ptr<T>
は、std::shared_ptr<T>
のhandleとして表示されます: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の優れた講演 これは、言語機能(この場合はスマートポインター)の最適な使用方法を説明し、デフォルトでリークの自由を示します(つまり、すべてが建設によって所定の位置にカチッと音を立てます;それを台無しにすることはほとんどできません)。必見です。