Search code examples
c++stdvectorstdthread

vector becomes empty when the operator() is executed by std::thread


I’m trying to create a thread (PrinStringManager) which in turn creates several threads (PrintEntry) (depending on the number of elements of the incoming vector of strings). Each PrintEntry thread created simply prints the string received in the constructor.

It is just a small example representing my problem.

class PrintEntry
{
public:
    PrintEntry(std::string& name) :name_(name) {}
    ~PrintEntry() {}
    void operator()() {
        std::cout << "name: " << name_;
    }
private:

    std::string name_;
};

class PrinStringManager
{
public:
    PrinStringManager(std::vector<std::string>& vec) :vec_(vec){}
    ~PrinStringManager() {
        for (auto& t : vt_) t.join();
    }

    void operator()() {
        for (auto name : vec_)
        {
            vt_.emplace_back(PrintEntry{ name });
        }
    }

private:

    std::vector<std::string> vec_;
    std::vector<std::thread> vt_;
};


void f()
{
    std::vector<std::string> vec{ "one","two", "three" };
    PrinStringManager psm{ vec };
    std::thread t{ std::ref(psm) };
    t.detach();
} 

int main()
{
    f();
    while (true)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
    std::cout << "Hello World!\n";
}

What it is happening, is that the object PrinStringManager is created with a vector of 3 strings but when the object function (from PrintStringmanager) is invokek by the thread, the vector is empty:

void operator()() {
    for (auto name : vec_) //<-- vec is empty!!
    {
        vt_.emplace_back(PrintEntry{ name });
    }
}

I noticed that when the end of scope for function f() is reached the destructor of PrinStringManager is called and that should be the problem.

Is there a way to o overcome this problem in a simply manner without setting PrinStringManager as static so that it lives forever?

Is there a way to move psm inside the thread object so that when the end of scope of f() is reached, the psm inside the thread hold the original value?


Solution

  • First make your object movable by adding a move constructor:

        // No implicitly declared one since you have a destructor, so bring it back
        PrinStringManager(PrinStringManager&&) = default;
        // Also the move assign is probably wanted
        PrinStringManager& operator=(PrinStringManager&&) = default;
    
    // (And also fix `PrintEntry`, probably by removing the destructor)
    

    Then you can make the thread hold its own PrinStringManager object, instead of merely a reference to one on the stack:

    // The thread will hold a PrinStringManager move-constructed from psm
    std::thread t{ std::move(psm) };
    

    If you can't make PrinStringManager movable for whatever reason, you can use dynamic allocation:

    void f()
    {
        std::vector<std::string> vec{ "one","two", "three" };
        auto psm = std::make_unique<PrinStringManager>(vec);
        // The thread holds a pointer to `psm` which is deleted
        // when the thread finishes
        std::thread t{ [psm=std::move(psm)]{ (*psm)(); } };
        t.detach();
    }