通过加入析构函数来等待异步工作是否安全?

Tav*_*nes 7 c++ multithreading thread-safety object-lifetime c++11

假设我有一个可以异步运行某些代码的类,并且该异步代码使用该类实例来执行调用成员函数,读取数据成员等操作.显然,类实例必须比后台线程更长,以便进行这些访问.安全.通过在析构函数中加入后台线程来确保这一点就足够了吗?例如:

#include <iostream>
#include <thread>

class foo final
{
public:
    foo() = default;

    void bar() {
        std::cout << "Hopefully there's nothing wrong with using " << this << "\n";
    }

    void bar_async() {
        if (!m_thread.joinable()) {
            m_thread = std::thread{&foo::bar, this};
        }
    }

    ~foo() {
        if (m_thread.joinable()) {
            std::cout << "Waiting for " << m_thread.get_id() << "\n";
            m_thread.join();
        }
    }

private:
    std::thread m_thread;
};

int main() {
    foo f;
    f.bar_async();
}
Run Code Online (Sandbox Code Playgroud)

具体来说,我担心对象生存期规则:

对于析构函数不是微不足道的类类型的任何对象,生命周期在析构函数的执行开始时结束.

...在对象的生命周期结束之后,在重用或释放对象占用的存储之前,以下使用标识该对象的glvalue表达式是未定义的:...

  • 访问非静态数据成员或调用非静态成员函数.

但对我来说,严格阅读上述内容也意味着this->bar()从内部~foo()直接调用是不明确的,"显然"并非如此.

Nat*_*ica 3

cppreference 是正确的,但它谈论的是从对象访问成员,而不是从析构函数内部访问。如果我们看一下 [class.cdtor]/1 我们会看到

对于具有非平凡构造函数的对象,在构造函数开始执行之前引用该对象的任何非静态成员或基类会导致未定义的行为。对于具有非平凡析构函数的对象,在析构函数完成执行后引用该对象的任何非静态成员或基类都会导致未定义的行为

强调我的

因此,只要我们在析构函数中,我们仍然可以使用成员对象,因为在析构函数的范围结束之前这些对象不会被销毁。

所以,join在这里调用线程就可以了。如果你想一想,如果不是的话,如果访问它们引用的互斥体是未定义的行为,那么像锁保护这样的东西将毫无用处。