c++ - 初期化 - スマートポインタ 実装




std:: weak_ptrはいつ有用ですか? (9)

私はC ++ 11のスマートポインタを勉強し始めました。私はstd::weak_ptr有用な使い方を見ていません。 誰かが私にstd::weak_ptrが有用/必要な時を教えてもらえますか?


@jleahyが私に与えた一例を示します。タスクの集まりがあり、非同期に実行され、 std::shared_ptr<Task>によって管理されているとします。 これらのタスクを使って何かを定期的に実行したいかもしれないので、タイマーイベントはstd::vector<std::weak_ptr<Task>>をトラバースし、タスクに何かを与えるかもしれません。 しかし同時に、タスクはもはや必要なくなり死に至ると同時に決定しているかもしれない。 したがって、タイマーは、弱ポインタ​​からの共有ポインタを作成し、それがヌルでないならば、その共有ポインタを使用することによって、タスクがまだ生きているかどうかをチェックすることができる。


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の循環参照を中断するために使用されます。


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());

shared_ptr :実オブジェクトを保持します。

weak_ptrlockを使って実際の所有者に接続するか、そうでない場合はNULLを返します。

大まかに言えば、 weak_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( not 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

すでに述べた他の有効なユースケースとは別に、 std::weak_ptrはマルチスレッド環境では素晴らしいツールです。

  • オブジェクトを所有していないため、別のスレッドで削除を妨げることはできません
  • std::shared_ptrstd::weak_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_ptrstd::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が所有するポインタが期限切れでないかどうかをチェックするので、安全にパスを変更したりマネージャを破棄したりすることができます。


共有ポインタの欠点があります:shared_pointerは親子サイクルの依存関係を処理できません。 子クラスが親クラスのオブジェクトを使用する場合、親クラスが共有ポインタを使用して子クラスのオブジェクトを使用する場合、同じファイル内にあることを意味します。 共有ポインタがすべてのオブジェクトを破壊するのに失敗し、共有ポインタがサイクル依存関係シナリオでデストラクタを呼び出していなくても。 基本的に共有ポインタは参照カウント機構をサポートしていません。

weak_pointerを使ってこの欠点を克服することができます。


私はstd::weak_ptr<T> std::shared_ptr<T> ハンドルとしてstd::weak_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.
        }
    }
};

もう1つの重要なシナリオは、データ構造内のサイクルを中断することです。

// 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は、 デフォルトでLeak Freedomを確実にするために、言語機能(この場合はスマートポインタ)を最大限に活用する方法について説明しています。 それは必見です。


非同期ハンドラが呼び出されたときにターゲットオブジェクトがまだ存在することが保証されていない場合、Boost.Asioで便利です。 このトリックは、 std::bindまたはlambdaキャプチャを使用してweak_ptrをasynchonousハンドラオブジェクトに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()イディオムの変形です。この例では、保留中の非同期ハンドラがターゲットオブジェクトの存続期間を延ばさ 、ターゲットオブジェクトが削除されても安全です。





weak-ptr