簡體   English   中英

C++17 和異步成員函數調用移動捕獲 lambda 表達式

[英]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
            }
        );
    }
);

這種模式就像https://www.boost.org/doc/libs/1_70_0/doc/html/boost_asio/reference/basic_stream_socket/async_read_some.html

我認為這是安全的,因為 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_funcother_async_func 移動發生兩次。 因此,第一級( async_funcshared_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

case1 的約束是安全的

*1 但是,在 case1 中,當且僅當struct object不期望它由 shared_ptr 持有時,它才是安全的。 換句話說,只要struct object不使用shared_from_this機制,就是安全的。

另一種控制順序的方法。 (支持 C++14)

當且僅當滿足上述約束時,我們可以在沒有 C++17 序列定義的情況下控制求值序列。 它支持 case1 和 case3。 只需獲取由 shared_ptr 持有的指針對象的引用。 關鍵是即使 shared_ptr 被移動,指針對象也會被保留。 所以在shared_ptr移動之前先獲取pointee對象的引用,然后shared_ptr移動,pointee對象不受影響。

但是, shared_from_this 是例外情況。 它直接使用 shared_ptr 機制。 所以這受 shared_ptr 移動的影響。 因此它是不安全的。 這就是約束的原因。

情況1

// 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
            }
        );
    }
);

案例3

// 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這是安全的。


使用這些知識:

  • 情況 1 可能會出現段錯誤並且絕對不安全,因為在移動的shared_ptr上調用operator->()返回nullptr ,然后您調用->async_func
  • 案例2是安全的只有調用async_func上移,從some_type是安全的,但它是不太可能它你打算什么除非類型實際上並沒有定義移動構造函數。
  • 情況 3 不安全,因為雖然可以在取消引用后移動共享指針(因為移動共享指針不會改變它指向的對象),但 C++ 不保證首先評估哪個函數參數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM