In the other question I've asked, I've learned some of evaluation orders are well defined since C++17. postfix-expression such as a->f(...)and a.b(...) are the part of them. See https://timsong-cpp.github.io/cppwp/n4659/expr.call#5
In the Boost.Asio, the following style asynchronous member function call is typical patter.
auto sp_object = std::make_shared<object>(...);
sp_object->async_func(
params,
[sp_object]
(boost::syste_error_code const&e, ...) {
if (e) return;
sp_object->other_async_func(
params,
[sp_object]
(boost::syste_error_code const&e, ...) {
if (e) return;
// do some
}
);
}
);
I'd like to clarify the following three cases's safety.
Case1: shared_ptr move and member function
auto sp_object = std::make_shared<object>(...);
sp_object->async_func(
params,
[sp_object = std::move(sp_object)]
(boost::syste_error_code const&e, ...) mutable { // mutable is for move
if (e) return;
sp_object->other_async_func(
params,
[sp_object = std::move(sp_object)]
(boost::syste_error_code const&e, ...) {
if (e) return;
// do some
}
);
}
);
This pattern is like https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/reference/basic_stream_socket/async_read_some.html
I think it is safe because the postfix-expression -> is evaluated before sp_object = std::move(sp_object).
Case2: value move and member function
some_type object(...);
object.async_func(
params,
[object = std::move(object)]
(boost::syste_error_code const&e, ...) mutable { // mutable is for move
if (e) return;
object.other_async_func(
params,
[object = std::move(object)]
(boost::syste_error_code const&e, ...) {
if (e) return;
// do some
}
);
}
);
I think is is dangerous because even if the postfix-expression . is evaluated before object = std::move(object), async_func may access the member of object.
Case3: shared_ptr move and free function
auto sp_object = std::make_shared<object>(...);
async_func(
*sp_object,
params,
[sp_object = std::move(sp_object)]
(boost::syste_error_code const&e, ...) mutable { // mutable is for move
if (e) return;
other_async_func(
*sp_object,
params,
[sp_object = std::move(sp_object)]
(boost::syste_error_code const&e, ...) {
if (e) return;
// do some
}
);
}
);
This pattern is like https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/reference/async_read/overload1.html
I think it is dangerous because there is no postfix-expression. So sp_object could be moved by third argument move capture before dereference as *sp_object by the first argument.
conclusion
Only case1 is safe and others are dangerous (undefined behavior). I need to be careful that It is unsafe on C++14 and older compilers. It can speed up calling asynchronous member function because shared_ptr's atomic counter operation is not happened. See Why would I std::move an std::shared_ptr? But I also need to consider that advantage could be ignored, it is depends on the application.
Am I understanding correctly about C++17 evaluation order change (precise definition) and asynchronous operation relationship?
Case:1is safe? thoughsp_object->async_funcevaluated beforesp_object = std::move(sp_object)....whether it is undefined or not is totally depends onasync_func's definition, what do you think would happen whenasync_funcaccessshared_from_this()?sp_object = std::move(sp_object)keep the same pointee object. Moved tosp_object.get()returns the same address (object) ofthisisasync_func(). So I believe thatshared_from_this()works fine. Here is working example wandbox.org/permlink/NooUkn4SUSAOPLDUstddo have to say "10) Move-constructs a shared_ptr from r. After the construction,*thiscontains a copy of the previous state of r, r is empty and its stored pointer isnull. The template overload doesn't participate in overload resolution if Y* is not implicitly convertible to (until C++17)compatible with (since C++17) T*."....so not sure what you've been seeing is well-defined.sp_object->is replaced to the address ofsp_object.get(). Let's say the address isaddr_object. So that means it is replaced asaddr_object->async_read. After this replacing,sp_objectbecome empty. But it is no problem.