Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it safe to wait for asynchronous work by joining in the destructor?

Suppose I have a class that may run some code asynchronously, and that asynchronous code uses that class instance to do things like call member functions, read data members, etc. Obviously the class instance must outlive the background thread in order for those accesses to be safe. It is sufficient to ensure this by joining the background thread in the destructor? For example:

#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();
}

Specifically, I'm worried about object lifetime rules:

For any object of class types whose destructor is not trivial, lifetime ends when the execution of the destructor begins.

... after the lifetime of an object has ended and before the storage which the object occupied is reused or released, the following uses of the glvalue expression that identifies that object are undefined: ...

  • Access to a non-static data member or a call to a non-static member function.

But to me, a strict reading of the above would also imply that calling this->bar() from inside ~foo() directly is undefined, which is "obviously" not the case.

like image 699
Tavian Barnes Avatar asked Sep 12 '17 16:09

Tavian Barnes


People also ask

Does destructor get called automatically?

A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ).

In which order the destructor are executed?

The body of an object's destructor is executed, followed by the destructors of the object's data members (in reverse order of their appearance in the class definition), followed by the destructors of the object's base classes (in reverse order of their appearance in the class definition).

Can destructor declared in private section?

Destructors with the access modifier as private are known as Private Destructors. Whenever we want to prevent the destruction of an object, we can make the destructor private. What is the use of private destructor? Whenever we want to control the destruction of objects of a class, we make the destructor private.


1 Answers

cppreference is right but it is talking about accessing the members from the object, not from inside the destructor. If we look at [class.cdtor]/1 we see that

For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.

emphasis mine

So, as long as we are in the destructor we can still work with the member objects as those are not destroyed until the scope of the destructor ends.

So, calling join on the thread is fine here. If you think about it if it wasn't then things like lock guards would be useless if accessing the mutex they refer to was undefined behavior.

like image 69
NathanOliver Avatar answered Sep 29 '22 12:09

NathanOliver