[英]C++17 and asynchronous member functions calling with move capture lambda expression
在我問的另一個問題中,我了解到自 C++17 以來,一些評估順序是明確定義的。 后綴表達式如a->f(...)
和ab(...)
是其中的一部分。 見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
}
);
}
);
我認為這是安全的,因為 postfix-expression ->
在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
}
);
}
);
我認為 is 是危險的,因為即使 postfix-expression .
在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
通過第一個參數取消引用為*sp_object
。
結論
只有 case1 是安全的,其他是危險的(未定義的行為)。 我需要注意它在 C++14 和更舊的編譯器上是不安全的。 由於沒有發生shared_ptr的原子計數器操作,所以可以加快異步成員函數的調用。 請參閱為什么我要 std::move 一個 std::shared_ptr? 但我也需要考慮到可以忽略的優勢,這取決於應用程序。
我是否正確理解了 C++17 評估順序更改(精確定義)和異步操作關系?
感謝 Explorer_N 的評論。 我得到了答案。
我問“Case1 是安全的,但 Case2 和 Case3 是不安全的,對嗎?”。 但是,當且僅當滿足我稍后編寫的約束 (*1) 時Case1 是安全的。 這意味着Case1 通常是不安全的。
它取決於async_func()
這是一個不安全的案例:
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
struct object : std::enable_shared_from_this<object> {
object(boost::asio::io_context& ioc):ioc(ioc) {
std::cout << "object constructor this: " << this << std::endl;
}
template <typename Handler>
void async_func(Handler&& h) {
std::cout << "this in async_func: " << this << std::endl;
h(123); // how about here?
std::cout << "call shared_from_this in async_func: " << this << std::endl;
auto sp = shared_from_this();
std::cout << "sp->get() in async_func: " << sp.get() << std::endl;
}
template <typename Handler>
void other_async_func(Handler&& h) {
std::cout << "this in other_async_func: " << this << std::endl;
h(123); // how about here?
std::cout << "call shared_from_this in other_async_func: " << this << std::endl;
auto sp = shared_from_this();
std::cout << "sp->get() in other_async_func: " << sp.get() << std::endl;
}
boost::asio::io_context& ioc;
};
int main() {
boost::asio::io_context ioc;
auto sp_object = std::make_shared<object>(ioc);
sp_object->async_func(
[sp_object = std::move(sp_object)]
(int v) mutable { // mutable is for move
std::cout << v << std::endl;
sp_object->other_async_func(
[sp_object = std::move(sp_object)]
(int v) {
std::cout << v << std::endl;
}
);
}
);
ioc.run();
}
運行演示https://wandbox.org/permlink/uk74ACox5EEvt14o
我考慮過為什么第一個shared_from_this()
,但第二個調用在上面的代碼中拋出std::bad_weak_ptr
。 這是因為回調處理程序是直接從async_func
和other_async_func
。 移動發生兩次。 因此,第一級( async_func
) shared_from_this
失敗。
即使不直接從異步函數調用回調處理程序,它在多線程情況下仍然不安全。
這是一個不安全的代碼:
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
struct object : std::enable_shared_from_this<object> {
object(boost::asio::io_context& ioc):ioc(ioc) {
std::cout << "object constructor this: " << this << std::endl;
}
template <typename Handler>
void async_func(Handler&& h) {
std::cout << "this in async_func: " << this << std::endl;
ioc.post(
[this, h = std::forward<Handler>(h)] () mutable {
h(123);
sleep(1);
auto sp = shared_from_this();
std::cout << "sp->get() in async_func: " << sp.get() << std::endl;
}
);
}
template <typename Handler>
void other_async_func(Handler&& h) {
std::cout << "this in other_async_func: " << this << std::endl;
ioc.post(
[this, h = std::forward<Handler>(h)] () {
h(456);
auto sp = shared_from_this();
std::cout << "sp->get() in other_async_func: " << sp.get() << std::endl;
}
);
}
boost::asio::io_context& ioc;
};
int main() {
boost::asio::io_context ioc;
auto sp_object = std::make_shared<object>(ioc);
sp_object->async_func(
[sp_object = std::move(sp_object)]
(int v) mutable { // mutable is for move
std::cout << v << std::endl;
sp_object->other_async_func(
[sp_object = std::move(sp_object)]
(int v) {
std::cout << v << std::endl;
}
);
}
);
std::vector<std::thread> ths;
ths.reserve(2);
for (std::size_t i = 0; i != 2; ++i) {
ths.emplace_back(
[&ioc] {
ioc.run();
}
);
}
for (auto& t : ths) t.join();
}
運行演示: https : //wandbox.org/permlink/xjLZWoLdn8xL89QJ
*1 但是,在 case1 中,當且僅當struct object
不期望它由 shared_ptr 持有時,它才是安全的。 換句話說,只要struct object
不使用shared_from_this
機制,就是安全的。
當且僅當滿足上述約束時,我們可以在沒有 C++17 序列定義的情況下控制求值序列。 它支持 case1 和 case3。 只需獲取由 shared_ptr 持有的指針對象的引用。 關鍵是即使 shared_ptr 被移動,指針對象也會被保留。 所以在shared_ptr移動之前先獲取pointee對象的引用,然后shared_ptr移動,pointee對象不受影響。
但是, shared_from_this 是例外情況。 它直接使用 shared_ptr 機制。 所以這受 shared_ptr 移動的影響。 因此它是不安全的。 這就是約束的原因。
// The class of sp_object class doesn't use shared_from_this mechanism
auto sp_object = std::make_shared<object>(...);
auto& r = *sp_object;
r.async_func(
params,
[sp_object]
(boost::syste_error_code const&e, ...) {
if (e) return;
auto& r = *sp_object;
r.other_async_func(
params,
[sp_object]
(boost::syste_error_code const&e, ...) {
if (e) return;
// do some
}
);
}
);
// The class of sp_object class doesn't use shared_from_this mechanism
auto sp_object = std::make_shared<object>(...);
auto& r = *sp_object;
async_func(
r,
params,
[sp_object = std::move(sp_object)]
(boost::syste_error_code const&e, ...) mutable { // mutable is for move
if (e) return;
auto& r = *sp_object;
other_async_func(
r,
params,
[sp_object = std::move(sp_object)]
(boost::syste_error_code const&e, ...) {
if (e) return;
// do some
}
);
}
);
您的問題可以大大簡化為“以下是否安全”:
some_object.foo([bound_object = std::move(some_object)]() {
bound_object.bar()
});
從你鏈接的問題來看,標准說
參數評估的所有副作用在輸入函數之前排序
一個這樣的副作用是從 some_object move
- 所以這相當於:
auto callback = [bound_object = std::move(some_object)]() {
bound_object.bar()
}
some_object.foo(std::move(callback));
顯然,這在調用foo
方法之前移出了some_object
。 當且僅當在移動對象上調用foo
這是安全的。
使用這些知識:
shared_ptr
上調用operator->()
返回nullptr
,然后您調用->async_func
。async_func
上移,從some_type
是安全的,但它是不太可能它你打算什么除非類型實際上並沒有定義移動構造函數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.