Can C++ std::thread callable’s object pointer be invalidated after the thread begins execution?

I have this chunk of code…

A non-static member method (serving as callable for thread):

void Object::unregister()
{
   ...
}

and destructor like this:

Object::~Object()
{
  std::thread cleanup(&Object::unregister, this);
  cleanup.detach();
}

What I see as a problem is that I am running a thread with parameter this which become invalidated when destructor finishes, so I assume it is dangerous, because I do not have any garantee, that the cleanup thread already started – and this my subquestion – is safe if this would be invalidated (destructor finishes) before the call to unregister finishes completely (in other words is it ok, if it already started, but did not finish completely)?

I would say that answer is No as the copy of this pointer is used with callable, but I am not sure as the app behaves, like it does not mind and everything is OK.

If it is ok that thread just started and did not finish yet, is there any way to know that the thread is already running? Would usage of call to joinable() return me true just when the thread is already executing or it can return true before the thread’s execution started?

Is there any way how to do it safe and be sure that callable &Object::unregister and this will not be invalidated, because Object was destroyed meanwhile?

Answer

is safe if this would be invalidated (destructor finishes) before the call to unregister finishes completely (in other words is it ok, if it already started, but did not finish completely)?

No, it’s not safe.

Consider the following C code:

void Object_unregister(void* obj)
{
    Object* this = (Object*)obj;
    fclose(this->file_handle);
    while (this->ref_counter > 0) {
        fclose(this->ref_array[this->ref_counter]->handle);
        free(this->ref_array[this->ref_counter]);
        this->ref_array[this->ref_counter] = NULL;
        --this->ref_counter;
    }
}

void destroy_Object(Object** this)
{
    pthread_t thread;
    pthread_create(&thread, NULL, &Object_unregister, (void*)*this);
    pthread_detach(&thread);
    free(*this);
    *this = NULL;
}

This is, at a very basic level, what your C++ code is doing. In this code, we create the thread, then detach it, then immediately free the memory space where the Object was at. In this way, there’s no guarantee that the this pointer in the Object_unregister function will point to the same Object that was passed to it.

There is a (general) guarantee that the thread function will still point to the same function pointer address the thread was created with, and that it will run until that function has completed, and in the above code, there is a guarantee that the this pointer will point to the same memory address from when the function was called.

But …

this could point to 0xABADCAFE and this->file_handle will point to this + sizeof(Object::file_handle), but if you’ve deleted the object, then what is actually at that address could no longer point to a valid reference of an Object type.

It could point to some random bit of encryption code, or a new function, or just about anything, but it could still point to the object that was originally there if that memory space was not reallocated by the kernel.

So no, it’s not safe.

Is there any way how to do it safe and be sure that callable &Object::unregister and this will not be invalidated, because Object was destroyed meanwhile?

Well it depends on what your Object::unregister code actually does in the context of the rest of your code. It’s not immediately clear why you want to thread the destructor and you don’t just call this->unregister(); in the destructor, example:

Object::~Object()
{
    this->unregister();
}

That’s as safe as you can get in the context of your code.

But if there’s other things that necessarily need to be done in a threaded way, you could do many architectural things to thread the destruction of the object, from static values to locking mechanisms, but essentially what you would need to do is make copies of the specific values you need to unregister so they remain valid in your thread code.

Leave a Reply

Your email address will not be published. Required fields are marked *