20

このパターンはコードでよく見られます。shared_from_thisメンバー関数への最初のパラメーターとしてバインドし、関数を使用して結果をディスパッチしasync_*ます。別の質問の例を次に示します。

void Connection::Receive()
{
     boost::asio::async_read(socket_,boost::asio::buffer(this->read_buffer_),
        boost::bind(&Connection::handle_Receive, 
           shared_from_this(),
           boost::asio::placeholders::error,
           boost::asio::placeholders::bytes_transferred));
 }

shared_from_this()代わりに使用する唯一の理由thisは、メンバー関数が呼び出されるまでオブジェクトを存続させることです。しかし、どこかに何らかのブーストマジックがない限り、thisポインターはタイプであるため、それでConnection*すべてhandle_Receiveを実行できます。返されたスマートポインターは、すぐに通常のポインターに変換する必要があります。それが起こった場合、オブジェクトを存続させるものは何もありません。そしてもちろん、を呼び出す際のポインタはありませんshared_from_this

しかし、私はこのパターンを頻繁に見たので、私が思うほど完全に壊れているとは信じられません。後で操作が完了したときに、shared_ptrを通常のポインターに変換するBoostマジックはありますか?もしそうなら、これはどこかに文書化されていますか?

特に、操作が完了するまで共有ポインターが存在し続けることがどこかに文書化されていますか?強力なポインターを呼び出しget_pointerてから、返されたポインターでメンバー関数を呼び出すだけでは、メンバー関数が戻るまで強力なポインターが破棄されない限り、十分ではありません。

4

5 に答える 5

21

つまり、から返されるのboost::bindコピーを作成し、ハンドラーのコピーを作成する場合があります。ハンドラーのコピーは、次のいずれかが発生するまで存続します。boost::shared_ptr<Connection>shared_from_this()boost::asio

  • ハンドラーは、サービスのrun()、、、またはメンバー関数が呼び出されたスレッドによって呼び出されました。run_one()poll()poll_one()
  • io_serviceが破壊されます。
  • io_service::serviceハンドラーを所有するは、を介してシャットダウンされますshutdown_service()

ドキュメントからの関連する抜粋は次のとおりです。

  • boost :: bindドキュメント

    取る引数はbind、返された関数オブジェクトによって内部的にコピーおよび保持されます。

  • boost :: asio io_service::post

    ハンドラーが、、、、またはメンバー関数が現在呼び出されているスレッドでのみ呼び出されるio_serviceことを保証します。[...]は、必要に応じてハンドラオブジェクトのコピーを作成します。run()run_one()poll()poll_one()io_service

  • boost :: asio io_service::~io_service

    io_service、または関連するストランドで の遅延呼び出しがスケジュールされた、呼び出されていないハンドラーオブジェクトは破棄されます。

    オブジェクトの有効期間が接続の有効期間(またはその他の非同期操作のシーケンス)にshared_ptr関連付けられている場合、オブジェクトへのaは、それに関連付けられているすべての非同期操作のハンドラーにバインドされます。[...]単一の接続が終了すると、関連するすべての非同期操作が完了します。対応するハンドラオブジェクトが破棄さshared_ptrれ、オブジェクトへのすべての参照が破棄されます。


日付(2007年)の間、TR2(リビジョン1)のネットワークライブラリ提案はBoost.Asioから派生しました。セクションでは、関数5.3.2.7. Requirements on asynchronous operationsの引数について詳しく説明します。async_

この句では、非同期操作は、接頭辞が付いた名前の関数によって開始されますasync_。これらの機能は、開始機能と呼ばれます。[...]ライブラリの実装はハンドラー引数のコピーを作成する場合があり、元のハンドラー引数とすべてのコピーは交換可能です。

関数を開始するための引数の存続期間は、次のように扱われるものとします。

  • パラメータがconst参照または値による宣言として宣言されている場合[...]実装は引数のコピーを作成でき、すべてのコピーはハンドラーの呼び出し直後までに破棄されるものとします。

[...]開始関数の引数に関連付けられた関数に対してライブラリ実装によって行われるすべての呼び出しは、呼び出しがシーケンス呼び出し1で発生してnを呼び出すように実行されます。ここで、すべてのi、1≤i <nについて、呼び出しi先行ますi+1を呼び出します。

したがって:

  • 実装により、ハンドラーのコピーが作成される場合があります。この例では、コピーされたハンドラーがのコピーを作成し、ハンドラーのコピーが存続している間、インスタンスshared_ptr<Connection>の参照カウントを増やします。Connection
  • 実装は、ハンドラーを呼び出す前にハンドラーを破棄する場合があります。これは、がシャットダウンされたとき、またはが破棄されたときに非同期操作が未処理の場合に発生します。この例では、ハンドラーのコピーが破棄され、の参照カウントが減少し、インスタンスが破棄される可能性があります。io_serive::serviceio_serviceConnectionConnection
  • ハンドラーが呼び出された場合、実行がハンドラーから戻ると、ハンドラーのすべてのコピーがすぐに破棄されます。この場合も、ハンドラーのコピーが破棄され、の参照カウントが減少し、破棄さConnectionれる可能性があります。
  • の引数に関連付けられた関数は、asnyc_並行してではなく、順次実行されます。これにはとが含まれio_handler_deallocateますio_handler_invokeこれにより、ハンドラーが呼び出されている間、ハンドラーの割り当てが解除されないことが保証されます。実装のほとんどの領域で、ハンドラーはスタック変数にコピーまたは移動されるため、実行が宣言されたブロックを終了すると破棄が発生します。この例では、これにより、ハンドラーの呼び出し中にの参照カウントが少なくとも1つになることが保証されます。boost::asioConnection
于 2012-07-07T03:36:53.427 に答える
5

こんなふうになります:

1)Boost.Bindのドキュメントには次のように記載されています。

「[注:mem_fnは、オブジェクトへのポインター、参照、またはスマートポインターを最初の引数として受け入れることができる関数オブジェクトを作成します。詳細については、mem_fnのドキュメントを参照してください。]」

2)mem_fnのドキュメントには次のように書かれています:

関数オブジェクトが、ポインターでも適切なクラス(上記の例ではX)への参照でもない最初の引数xで呼び出されると、get_pointer(x)を使用してxからポインターを取得します。ライブラリの作成者は、適切なget_pointerオーバーロードを提供することで、スマートポインタクラスを「登録」し、mem_fnがそれらを認識してサポートできるようにすることができます。

したがって、ポインターまたはスマートポインターは、呼び出されるまで、そのままバインダーに格納されます。

于 2012-07-06T14:58:04.450 に答える
4

また、このパターンが頻繁に使用されていることもわかります。(@ Tannerのおかげで)が複数のスレッドio_serviceで実行されているときに使用される理由がわかります。ただし、クラッシュの可能性をメモリ/リソースリークの可能性に置き換えるため、ライフタイムの問題がまだ残っていると思います...

boost :: bindのおかげで、shared_ptrsにバインドされたコールバックはすべてオブジェクトの「ユーザー」になるため(オブジェクトのuse_countが増加します)、未処理のコールバックがすべて呼び出されるまでオブジェクトは削除されません。

boost :: asio :: async *関数へのコールバックは、関連するタイマーまたはソケットでcancelまたはcloseが呼び出されるたびに呼び出されます。通常は、Stroustrupの最愛のRAIIパターンを使用して、デストラクタで適切なキャンセル/クローズ呼び出しを行うだけです。仕事は終わりました。

ただし、コールバックはまだshared_ptrsのコピーを保持しているため、所有者がオブジェクトを削除してもデストラクタは呼び出されません。そのため、use_countはゼロより大きくなり、リソースリークが発生します。オブジェクトを削除する前に適切なキャンセル/クローズ呼び出しを行うことにより、リークを回避できます。ただし、RAIIを使用して、デストラクタでキャンセル/クローズ呼び出しを行うほど確実ではありません。例外が存在する場合でも、リソースが常に解放されるようにします。

RAII準拠のパターンは、コールバックに静的関数を使用し、以下の例のようにコールバック関数を登録するときにweak_ptrをboost::bindに渡すことです。

class Connection : public boost::enable_shared_from_this<Connection>
{
  boost::asio::ip::tcp::socket socket_;
  boost::asio::strand  strand_;
  /// shared pointer to a buffer, so that the buffer may outlive the Connection 
  boost::shared_ptr<std::vector<char> > read_buffer_;

  void read_handler(boost::system::error_code const& error,
                    size_t bytes_transferred)
  {
    // process the read event as usual
  }

  /// Static callback function.
  /// It ensures that the object still exists and the event is valid
  /// before calling the read handler.
  static void read_callback(boost::weak_ptr<Connection> ptr,
                            boost::system::error_code const& error,
                            size_t bytes_transferred,
                            boost::shared_ptr<std::vector<char> > /* read_buffer */)
  {
    boost::shared_ptr<Connection> pointer(ptr.lock());
    if (pointer && (boost::asio::error::operation_aborted != error))
      pointer->read_handler(error, bytes_transferred);
  }

  /// Private constructor to ensure the class is created as a shared_ptr.
  explicit Connection(boost::asio::io_service& io_service) :
    socket_(io_service),
    strand_(io_service),
    read_buffer_(new std::vector<char>())
  {}

public:

  /// Factory method to create an instance of this class.
  static boost::shared_ptr<Connection> create(boost::asio::io_service& io_service)
  { return boost::shared_ptr<Connection>(new Connection(io_service)); }

  /// Destructor, closes the socket to cancel the read callback (by
  /// calling it with error = boost::asio::error::operation_aborted) and
  /// free the weak_ptr held by the call to bind in the Receive function.
  ~Connection()
  { socket_.close(); }

  /// Convert the shared_ptr to a weak_ptr in the call to bind
  void Receive()
  {
    boost::asio::async_read(socket_, boost::asio::buffer(read_buffer_),
          strand_.wrap(boost::bind(&Connection::read_callback,
                       boost::weak_ptr<Connection>(shared_from_this()),
                       boost::asio::placeholders::error,
                       boost::asio::placeholders::bytes_transferred,
                       read_buffer_)));
  }
};

注:はConnectionクラスにread_buffer_として格納され、関数にとしてshared_ptr渡されます。 read_callbackshared_ptr

これは、複数io_servicesが別々のタスクで実行されている場合、他のタスクが完了するまで、つまり関数が呼び出される までread_buffer_が削除されないようにするためです。read_callback

于 2013-10-27T18:56:29.203 に答える
2

正しく指摘したように安全ではないため、 ( boost::shared_ptr<Connection>return type of shared_from_this)からConnection*( type of)への変換はありません。this

魔法はBoost.Bindにあります。簡単に言うと、がメンバーへのポインタであるフォームの呼び出しbind(f, a, b, c)(この例ではプレースホルダーまたはネストされたバインド式は含まれません)でf、呼び出しの結果を呼び出すと、型が派生している(a.*f)(b, c)場合はフォームの呼び出しになりますaポインタのクラスタイプからメンバー(またはタイプboost::reference_wrapper<U>)へ、またはの形式((*a).*f)(b, c)です。これは、ポインターとスマートポインターで同様に機能します。(私は実際にメモリからstd::bind、Boost.Bindのルールを完全に同一ではありませんが、両方とも同じ精神で作業しています。)

さらに、の結果はshared_from_this()への呼び出しの結果に保存され、bind生涯の問題がないことを保証します。

于 2012-07-06T07:44:14.193 に答える
1

ここで明らかな何かが欠けているかもしれませんが、によって返されるshared_ptrは、によってshared_from_this()返される関数オブジェクトに格納されboost::bind、それが存続します。これは、非同期読み取りが終了したときにコールバックが起動されたときにのみ暗黙的に変換さConnection*れ、オブジェクトは少なくとも呼び出しの間は存続します。がhandle_Receiveこれから別のshared_ptrを作成せず、バインドファンクターに格納されたshared_ptrが最後に存続しているshared_ptrである場合、コールバックが戻った後にオブジェクトは破棄されます。

于 2012-07-06T07:41:38.940 に答える