C++17和异步成员函数使用move-capture lambda表达式调用

C++17和异步成员函数使用move-capture lambda表达式调用,c++,boost,c++17,move,asio,C++,Boost,C++17,Move,Asio,在我的提问中,我了解到一些评估顺序是自C++17以来定义良好的。后缀表达式,如a->f…和a.b…是它们的一部分。看 在中,以下样式的异步成员函数调用是典型的模式 auto sp_object = std::make_shared<object>(...); sp_object->async_func( params, [sp_object] (boost::syste_error_code const&e, ...) { if

在我的提问中,我了解到一些评估顺序是自C++17以来定义良好的。后缀表达式,如a->f…和a.b…是它们的一部分。看

在中,以下样式的异步成员函数调用是典型的模式

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
            }
        );
    }
);
我认为这是危险的,因为即使是后缀表达式。在object=std::moveobject之前求值,async_func可以访问object的成员

案例3:共享ptr移动和自由功能

这种模式就像

我认为这很危险,因为没有后缀表达式。因此,sp_对象可以通过第三个参数move capture移动,然后通过第一个参数作为*sp_对象解除引用

结论

只有情况1是安全的,其他情况是危险的未定义行为。 我需要注意的是,它在C++14和更旧的编译器上是不安全的。 它可以加快调用异步成员函数的速度,因为shared_ptr的原子计数器操作没有发生。看见 但我也需要考虑这个优点可以忽略,这取决于应用程序。 我是否正确理解C++17评估顺序更改的精确定义和异步操作关系?

答案 多亏了探险家的评论。我得到了答案

我问过案例1是安全的,但案例2和案例3是不安全的,是吗?。然而,当且仅当满足我稍后编写的约束*1时,Case1是安全的。这意味着案例1通常是不安全的

这取决于异步函数

这是一个不安全的案例:

#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();
}
正在运行演示:

案例1的约束是安全的 *一, 然而,在案例1中,如果且仅当struct对象不期望它被shared_ptr持有,那么它是安全的。换句话说,只要struct对象不使用这个机制中的shared_,它就是安全的

控制顺序的另一种方法。支持C++14 当且仅当满足上述约束时,我们可以在不使用C++17序列定义的情况下控制求值序列。 它支持案例1和案例3。只需获取由shared_ptr持有的指针对象的引用。关键的一点是,即使移动了共享的ptr,指针对象也会被保留。因此,在移动共享\u ptr之前获取指针对象的引用,然后移动共享\u ptr,指针对象不受影响

然而,这是一个例外情况。它直接使用共享的ptr机制。因此,这受共享ptr移动的影响。因此它是不安全的。这就是限制的原因

案例1 案例3
您的问题可以大大简化为以下安全问题:

some_object.foo([bound_object = std::move(some_object)]() {
    bound_object.bar()
});
从你的链接问题中

参数求值的所有副作用在输入函数之前都按顺序排列

其中一个副作用是从某个对象移动,因此这相当于:

auto callback = [bound_object = std::move(some_object)]() {
    bound_object.bar()
}
some_object.foo(std::move(callback));
显然,在调用foo方法之前,它会移出一些_对象。这是安全的,当且仅当对已从对象移动的对象调用foo时

利用这些知识:

案例1可能会出现segfault,而且肯定不安全,因为在从共享移动的\u ptr上调用操作符->会返回null ptr,然后调用它->异步\u func on。 只有在从某个类型移动的对象上调用async_func是安全的情况下,情况2才是安全的,但除非该类型实际上没有定义移动构造函数,否则它不太可能达到您的目的。 案例3是不安全的,因为在取消引用之后移动共享指针是可以的,因为共享指针不改变它指向的对象,C++不能保证首先对哪个函数参数进行评估。
首先,你为什么认为案例1是安全的?虽然sp_object->async_func在sp_object=std::movesp_object之前计算过……它是否未定义完全取决于async_func的定义,但您认为当async_func从此访问共享_时会发生什么?因为sp_object=std::movesp_object保留相同的指针对象。移到sp_object.get返回与此对象相同的地址对象is async_func。因此,我相信,从_分享_这很好。这是一个移动共享ptr的工作示例,这是std必须说的10个移动从r构造一个共享ptr。在构造之后,*它包含以前状态r的副本,r为空,其存储的指针为null。如果Y*在C++17兼容之前不能隐式转换为,那么模板重载不会参与重载解析,因为C++17 t*…所以不确定您看到的是定义良好的。我认为这意味着在sp_object->被替换为sp_object.get的地址。假设地址是addr_object。这意味着它被替换为addr\u object->async\u read。替换后,sp_对象变为空。但这没问题。我稍微改变了一下你的例子,它的行为取决于我们调用的函数的功能,如果我遗漏了什么,请告诉我。获取了以类型为std::\u 1::bad\u-weak\u-ptr:bad\u-weak\u-ptr的未捕获异常终止的此操作
你是说第一个代码和第二个代码是等效的吗?更新后的代码明确涵盖了cases@TakatoshiKondo您可以接受这一点作为答案,它清楚地说明了在执行之前进行评估,对我来说这听起来很关键。这是正在运行的代码:如果使用moved from对象调用foo不安全,则它是不安全的。我同意这一点。这是我问题的第二种情况。在案例1中,一些对象是共享的。看看是否并且仅当某个_类不希望我被shared_ptr持有时,我认为这是安全的。我认为有上述限制的案例1是安全的。这就是我的观点。@TakatoshiKondo:你说得对,基于这个理由,案例1看起来还是安全的
#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();
}
#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();
}
// 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()
});
auto callback = [bound_object = std::move(some_object)]() {
    bound_object.bar()
}
some_object.foo(std::move(callback));