12

boost::signals2 は、接続されたスロットの遅延削除のようなものを使用しているため、接続をオブジェクトの有効期間を管理するものとして使用することが難しくなっていることがわかりました。切断されたときにスロットを強制的に直接削除する方法を探しています。コードを別の方法で設計して問題を回避する方法についてのアイデアも大歓迎です!

これが私のシナリオです。非同期に時間がかかる処理を担当する Command クラスがあり、次のようになります (簡略化)。

class ActualWorker {
public:
    boost::signals2<void ()> OnWorkComplete;
};

class Command : boost::enable_shared_from_this<Command> {
public:
    ...

    void Execute() {
        m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind(&Command::Handle_OnWorkComplete, shared_from_this());

        // launch asynchronous work here and return
    }

    boost::signals2<void ()> OnComplete;

private:
    void Handle_OnWorkComplete() {
        // get a shared_ptr to ourselves to make sure that we live through
        // this function but don't keep ourselves alive if an exception occurs.
        shared_ptr<Command> me = shared_from_this();

        // Disconnect from the signal, ideally deleting the slot object
        m_WorkerConnection.disconnect();

        OnComplete();

        // the shared_ptr now goes out of scope, ideally deleting this
    }

    ActualWorker m_MyWorker;
    boost::signals2::connection m_WorkerConnection;
};

クラスは次のように呼び出されます。

...
boost::shared_ptr<Command> cmd(new Command);
cmd->OnComplete.connect( foo );
cmd->Execute();
// now go do something else, forget all about the cmd variable etcetera.

Command クラスは、boost::bind を使用して ActualWorker シグナルにバインドされた shared_ptr をそれ自体に取得することによって、自身を存続させます。

ワーカーが完了すると、Command のハンドラーが呼び出されます。ここで、Command オブジェクトを破棄したいので、上記のコードに見られるように、シグナルから切断します。問題は、切断時に実際のスロット オブジェクトが削除されず、無効としてマークされ、後で削除されることです。これは、再び発火する信号に依存しているように見えますが、私の場合はそうではなく、スロットが期限切れになることはありません。したがって、boost::bind オブジェクトはスコープから外れることはなく、削除されることのないオブジェクトへの shared_ptr を保持します。

これを回避するには、shared_ptr の代わりに this ポインターを使用してバインドし、メンバー shared_ptr を使用してオブジェクトを存続させてから、ハンドラー関数で解放しますが、設計が少し複雑すぎるように感じます。切断時にsignals2に強制的にスロットを削除させる方法はありますか? または、デザインを簡素化するために他にできることはありますか?

どんなコメントでも大歓迎です!

4

5 に答える 5

3

boost::signals2接続/呼び出し中にスロットをクリーンアップします。

したがって、すべてのスロットがシグナルから切断された場合、もう一度シグナルを呼び出しても何も呼び出されませんが、スロットはクリーンアップされます。

あなたのコメントに答えるには、はい、他のスロットが接続されている場合、信号を再度呼び出すことは安全ではありません。それらは再度呼び出されるためです。その場合は、逆にダミー スロットを接続し、「実際の」スロットが呼び出されたときに切断することをお勧めします。別のスロットを接続すると古い接続がクリーンアップされるため、スロットを解放する必要があります。

ダミースロットに解放が必要な参照を保持していないことを確認してください。そうしないと、最初の場所に戻ります。

于 2010-02-15T12:48:32.127 に答える
2

これは、boost::signals2 の信じられないほど厄介な側面です。

これを解決するためにとったアプローチは、信号を scoped_ptr に格納することであり、すべてのスロットを強制的に切断したい場合は、信号を削除します。これは、信号へのすべての接続を強制的に切断したい場合にのみ機能します。

于 2010-06-19T02:37:31.580 に答える
1

シグナルの独自の (サブセット) 実装を行うことになりました。主な要件は、connection::disconnect() の呼び出しによってスロットを破棄することです。

実装は、リスト/ベクトルではなく、スロット実装ポインターからスロット実装の shared_ptr へのマップ内のすべてのスロットを格納する信号の線に沿って進みます。これにより、すべてのスロットを反復する必要なく、個々のスロットにすばやくアクセスできます。私の場合、スロットの実装は基本的にboost::関数です。

接続には、シグナルの内部実装クラスへの weak_ptr とスロット実装タイプへの weak_ptr があり、シグナルがスコープ外に出て、スロット ポインターをシグナル マップへのキーとして使用できるようになります。接続はまだアクティブです (生のポインターは再利用される可能性があるため使用できません)。

disconnect が呼び出されると、これらの弱いポインターの両方が shared_ptrs に変換され、これらの両方が成功した場合、シグナルの実装は、ポインターによって指定されたスロットを切断するように求められます。これは、マップから単純に消去することによって行われます。

マップは、マルチスレッドで使用できるようにミューテックスによって保護されています。デッドロックを防ぐために、スロットの呼び出し中にミューテックスは保持されませんが、これは、シグナルによって呼び出される直前にスロットが別のスレッドから切断される可能性があることを意味します。これは通常の boost::signals2 にも当てはまり、これらのシナリオの両方で、シグナルが切断された後でもシグナルからのコールバックを処理できる必要があります。

シグナルが発生したときのコードを単純化するために、この間、すべてのスロットを強制的に切断しています。これは、シグナルの発火中に切断/接続を処理するためにスロットを呼び出す前にスロットのリストのコピーを行う boost::signals2 とは異なります。

上記は、目的のシグナルがめったに発生しない (その場合は 1 回だけ) という私のシナリオではうまく機能しますが、短命の接続が多数あり、それ以外の場合は、概説したトリックを使用した場合でも多くのメモリを使い果たします。質問。

他のシナリオでは、信号の使用を単にboost::関数に置き換えることができました(したがって、単一の接続のみが必要です)、またはリスナー自体が管理する質問の回避策に固執するだけですその寿命。

于 2010-12-20T14:58:54.797 に答える
1

scoped_connection を使用した場合、動作はより厳密になりますか?

したがって、ではなく:

void Execute() {
    m_WorkerConnection = m_MyWorker.OnWorkDone.connect(boost::bind
        (&Command::Handle_OnWorkComplete, shared_from_this());

    // launch asynchronous work here and return
}

...

boost::signals2::connection m_WorkerConnection;

代わりに次を使用します。

void Execute() {
    boost::signals2::scoped_connection m_WorkerConnection
        (m_MyWorker.OnWorkDone.connect(boost::bind
        (&Command::Handle_OnWorkComplete, shared_from_this()));

    // launch asynchronous work here and return
}   // connection falls out of scope

( からコピー構築boost::signals2::connection)

私は何らかのシグナル伝達を使用していないので、何よりも推測にすぎませんが、scoped_connection が処理するため、従うExecute()必要はありません。disconnect()これは、実際に問題を解決するというよりも、「設計を単純化する」という意味です。Execute()ただし、すぐに~Command()(またはdeleteshared_ptr)できることを意味する場合があります。

それが役立つことを願っています。

編集:そしてExecute()すぐに、~Command()私は明らかにあなたの Command オブジェクトの外側から意味します。Command を作成して実行すると、次のように言えるはずです。

cmd->Execute();
delete cmd;

または類似。

于 2010-01-18T02:28:10.373 に答える