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?