16

オブザーバーパターンSubjectから安全なクラスを書き込もうとしています。次のような方法でインスタンスを保存するのに、を使用するのが最善の方法であるかどうかを知りたいです。weak_ptrIObserver

  • IObserver解放されたインスタンスを使用することはできません。
  • Subjectクラスは、解放されるべき参照を保持しません(IObserverリスナーの失効の問題)。
  • クラスはSubjectスレッドセーフである必要があります。

残念ながら、私たちのコーディング標準では、ブーストの使用は許可されていません。私は前世で悪い人だったと思います。幸い、C ++ 11(Visual Studio 2012に付属しているもの)を使用できます。

これがサンプルObserverクラスです。

// Observer interface that supports notify() method
class IObserver
{
public:
    virtual void notify() const = 0;
    virtual ~IObserver() {}
};

// Concrete observer implementation that prints a message
class Observer : public IObserver
{
public:
    Observer( const std::string& message) : m_message( message ){}

    void notify() const {
        printf( "%s\r\n", m_message.c_str() );
    }

private:
    std::string m_message;
};

そして、これがSubjectクラスです。

// Subject which registers observers and notifies them as needed.
class Subject
{
public:
    // Use shared_ptr to guarantee the observer is valid right now
    void registerObserver( const std::shared_ptr<IObserver>& o )
    {
        std::lock_guard<std::mutex> guard( m_observersMutex );
        m_observers.push_back( o );
    }

    void unregisterObserver( const std::shared_ptr<IObserver>& o )
    {
        std::lock_guard<std::mutex> guard( m_observersMutex );
        // Code to remove the observer from m_observersMutex
    }

    // This is a method that is run in its own thread that notifies observers of some event
    void doNotify()
    {
        std::lock_guard<std::mutex> guard( m_observersMutex );
        // Notify any valid observers of events.
        std::for_each( m_observers.cbegin(), m_observers.cend(), 
            []( const std::weak_ptr<IObserver>& o )
        {
            auto observer = o.lock();
            if ( observer ) {
                observer->notify();
            } 
        } );

        // Remove any dead observers.  These are ones which have expired().
        m_observers.erase( std::remove_if( m_observers.begin(), m_observers.end(), 
            []( const std::weak_ptr<IObserver>& o )
        {
            return o.expired();
        } ), m_observers.end() );

    }


private:
    std::vector<std::weak_ptr<IObserver>> m_observers;
    std::mutex m_observersMutex;
};

演習するコードは次のSubjectとおりです。

int main(int argc, wchar_t* argv[])
{

    Subject subject;
    auto observerHello = std::make_shared<Observer>( "Hello world" );
    subject.registerObserver( observerHello );
    {
        // Create a scope to show unregistration.
        auto observerBye = std::make_shared<Observer>( "Good bye" );
        subject.registerObserver( observerBye );

        subject.doNotify();
    }
    printf( "%s\r\n", "Observer good bye is now be destructed" );
    subject.doNotify();
    return 0;
}

私のweak_ptrスレッドセーフの使用法はありますか?ここからhttps://stackoverflow.com/a/2160422/1517648そうだと思います

これは、失効したリスナーの問題を解決するための正当な方法ですか?

4

1 に答える 1

14

私はあなたの少し不安になるでしょうdoNotify-あなたが発砲したオブザーバーの何かがオブザーバーを追加または削除することになったとしたら?-悪いことが起こります(クラッシュを含む)。または、オブザーバーの追加をブロックする別のスレッドのアクションをブロックしますか?-悪いことが起こります(デッドロック!)

これを解決するのは難しいです。基本的に、それは再入可能性の問題です。

ロックを保持しているときは、コードの制御を離れないでください。コールバックの呼び出し中にロックを保持することはできません。

したがって、少なくとも:

ロックしてからリストをコピーしてからロックを解除します。このコピーを実行しているときに、期限切れのオブザーバーを(元のリストとコピーのリストの両方から)削除することもできます。

次に、コピーしたリストからオブザーバーを解雇します。

これにより、いくつかの問題が未解決のままになります。オブザーバーを削除しても、将来呼び出されないという保証はありません。それは、最終的には呼び出されないことを意味します。

それがどれほど重要かは、リスニングの使い方によって異なります。

動作する可能性のあるアプローチの1つは、add / remove / notify / killthreadイベントを含むタスクキューです(killthreadをキュー内のタスクにすると、シャットダウンの煩わしさが大幅に軽減されます)。これで、すべての同期がキューに入れられます。ノンブロッキングのロックフリーキューを作成する準備ができていない場合、通知コードは単にロック、std::moveキュー、ロック解除してから実行に進むことができます。popまたは、何かを読み取るまでブロックし、pushブロックしないようにキューを作成することもできます。

簡単で汚い「コピーとブロードキャスト」は次のようになります。

std::vector<std::shared_ptr<IObserver>> targets;
{
  std::lock_guard<std::mutex> guard( m_observersMutex );
  m_observers.erase( std::remove_if( m_observers.begin(), m_observers.end(), 
        [&targets]( const std::weak_ptr<IObserver>& o )
    {
      std::shared_ptr<IObserver> ptr = o.lock();
      if (ptr) {
        targets.push_back(ptr);
        return false;
      } else {
        return true;
      }
    } ), m_observers.end() );
}

for( auto& target:targets ) {
  target->notify();
}
于 2013-01-17T15:33:56.057 に答える