【发布时间】:2019-11-29 22:07:50
【问题描述】:
在the other question 中,我问过,我了解到一些评估顺序自 C++17 以来就已明确定义。诸如a->f(...)和a.b(...)之类的后缀表达式是其中的一部分。见https://timsong-cpp.github.io/cppwp/n4659/expr.call#5
在Boost.Asio中,以下风格的异步成员函数调用是典型的模式。
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
}
);
}
);
我想澄清以下三种情况的安全性。
案例 1:shared_ptr 移动和成员函数
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
}
);
}
);
我认为这是安全的,因为后缀表达式 -> 在 sp_object = std::move(sp_object) 之前被评估。
案例2:价值移动和成员函数
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
}
);
}
);
我认为这是危险的,因为即使在 object = std::move(object) 之前评估后缀表达式 .,async_func 也可以访问 object 的成员。
案例 3:shared_ptr 移动和释放函数
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
}
);
}
);
这个模式就像https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/reference/async_read/overload1.html
我认为这很危险,因为没有后缀表达式。因此,sp_object 可以通过第三个参数移动捕获移动,然后通过第一个参数取消引用为 *sp_object。
结论
只有 case1 是安全的,其他是危险的(未定义的行为)。 我需要小心它在 C++14 和更旧的编译器上是不安全的。 它可以加快调用异步成员函数,因为shared_ptr 的原子计数器操作没有发生。见Why would I std::move an std::shared_ptr? 但我也需要考虑到优势可以忽略不计,这取决于应用程序。
我对 C++17 求值顺序变化(精确定义)和异步操作关系的理解是否正确?
【问题讨论】:
-
首先,为什么你认为
Case:1是安全的?虽然sp_object->async_func在sp_object = std::move(sp_object)之前进行了评估....是否未定义完全取决于async_func的定义,您认为async_func访问shared_from_this()会发生什么? -
因为
sp_object = std::move(sp_object)保持相同的指针对象。移至sp_object.get()返回this的相同地址(对象)为async_func()。所以我相信shared_from_this()工作正常。这是工作示例wandbox.org/permlink/NooUkn4SUSAOPLDU -
在移动 shared_ptr 的情况下,
std必须说 "10) 从 r 移动构造一个 shared_ptr。构造之后,*this包含之前状态的副本r, r 为空且其存储的指针为null。如果 Y* 不能隐式转换为 (C++17 前)与 (C++17 起) T* 兼容,则模板重载不参与重载决议."....所以不确定您所看到的是否明确。 -
我认为timsong-cpp.github.io/cppwp/n4659/expr.call#5的意思是
sp_object->被替换为sp_object.get()的地址。假设地址是addr_object。这意味着它被替换为addr_object->async_read。替换后,sp_object变为空。不过没问题。 -
稍微改变了你的例子(对我来说,它的行为取决于我们调用的函数的作用)wandbox.org/permlink/Tv9pbhvls2ZkHJE7,如果我错过了什么,请告诉我。得到这个“以 std::__1::bad_weak_ptr: bad_weak_ptr 类型的未捕获异常终止”