How can I pass an object’s member variable (field) as a reference to a thread safely?

  c++, multithreading, thread-safety

Let’s say I start a new thread from a classmethod and pass "this" as a parameter to the lambda of the new thread. If the object is destroyed before the thread uses something from "this", then it’s probably undefined behavior.
As a simple example:

#include <thread>
#include <iostream>


class Foo
{
public:
    Foo() : m_bar{123} {}

    void test_1()
    {
        std::thread thd = std::thread{[this]()
        {
            std::cout << m_bar << std::endl;
        }};
        thd.detach();
    }

    void test_2()
    {
        test_2(m_bar);
    }
    void test_2(int & bar)
    {
        std::thread thd = std::thread{[this, & bar]()
        {
            std::cout << bar << std::endl;
        }};
        thd.detach();
    }

private:
    int m_bar;
};


int main()
{
    // 1)
    std::thread thd_outer = std::thread{[]()
    {
        Foo foo;
        foo.test_1();
    }};
    thd_outer.detach();

    // 2)
    {
        Foo foo;
        foo.test_1();
    }

    std::cin.get();
}

The outcomes

(For the original project, I have to use VS19, so the exception messages are originally coming from that IDE.)

  1. Starting from thd_outer, test_1 and test_2 are either throwing an exception (Exception thrown: read access violation.) or printing 0 (instead of 123).
  2. Without thd_outer they seem correct.

I’ve tried the same code with GCC under Linux, and they always print 123.

Which one is the correct behavior? I think it is UB, and in that case all are "correct". If it’s not undefined, then why are they different?

I would expect 123 or garbage always because either the object is still valid (123) or was valid but destroyed and a) the memory is not reused yet (123) or reused (garbage). An exception is reasonable but what exactly is throwing it (VS only)?

I’ve came up with a possible solution to the problem:

class Foo2
{
public:
    Foo2() : m_bar{123} {}
    ~Foo2()
    {
        for (std::thread & thd : threads)
        {
            try
            {
                thd.join();
            }
            catch (const std::system_error & e)
            {
                // handling
            }
        }
    }

    void test_1()
    {
        std::thread thd = std::thread{[this]()
        {
            std::cout << m_bar << std::endl;
        }};
        threads.push_back(std::move(thd));
    }

private:
    int m_bar;
    std::vector<std::thread> threads;
};

Is it a safe solution, without undefined behaviors? Seems like it’s working. Is there a better and/or more "standardized" way?

Source: Windows Questions C++

LEAVE A COMMENT