c++multithreadingmovemost-vexing-parse

move sematics in thread wrapper class with RAII in C++


I am trying to get a RAII wrapper class around std::thread to work in C++.

#include <iostream>
#include <thread>
#include <vector>

using std::cout;
using std::endl;

void threadFunction()
{
    for (int i = 0; i < 20; ++i) {
        cout << "thread function pointer executing..." << i << endl;
    }
}



// RAII wrapper that supports automatic join
class ThreadJoinRAII
{
    std::thread thread_;
public:
    ThreadJoinRAII(std::thread&& t):thread_(std::move(t)) 
    // ThreadJoinRAII()
    {
        cout << "ThreadJoinRAII ctor" << endl;
    }

    //ThreadJoinRAII(const ThreadJoinRAII& other) = delete;
    //ThreadJoinRAII& operator=(const ThreadJoinRAII& other) = delete;

    //ThreadJoinRAII(ThreadJoinRAII&& other):thread_(std::move(other.thread_))
    //{
    //    cout << "ThreadJoinRAII move ctor" << endl;
    //}
    //ThreadJoinRAII& operator=(ThreadJoinRAII&& other)
    //{
    //    cout << "ThreadJoinRAII move op" << endl;
    //    thread_ = std::move(other.thread_);
    //    return *this;
    //}
    
    ~ThreadJoinRAII()
    {
        cout << "in ThreadJoinRAII dtor" << endl;
        if (thread_.joinable()) { 
            thread_.join();
            cout << "join thread id = " << thread_.get_id() << endl;
        } else {
            cout << "thread not joinable. id=" << thread_.get_id() << endl;
        }
    }
};

class Something
{
    std::vector<int> vec_;
public:
    Something(std::vector<int>&& v):vec_(std::move(v)) {
        cout << "Something ctor" << endl;
    }

};


void testRAII()
{
    ThreadJoinRAII t1(std::move(std::thread(threadFunction))); // prints ThreadJoinRAII ctor
    ThreadJoinRAII t2(std::thread(threadFunction)); // prints nothing
    Something s1(std::vector<int>(3, 4)); // prints Something ctor
}

int main(int argc, char* argv[])
{
    testRAII();
    return 0;
}

The issue is that t2 line in testRAII() prints nothing. That part I don't understand. I tried to add/remove copy operations and move operations and they didn't make any difference.

My questions are:

  1. Isn't std::thread(threadFunction) is already a rvalue in testRAII()? Why do I have to move it for the constructor to work?
  2. What code gets invoked for t2 if the constructor provided was not used? I don't see ThreadJoinRAII ctor printed.
  3. s1 line prints out "Something ctor". What is the difference between t2 and s1? Is std::thread rvalue treated differently?

BTW, I compiled the code on Ubuntu 20.04 LTS with g++ 9.3 with g++ -std=c++17 -o mt mt.m.cpp -lpthread. mt.m.cpp is my file name.


Solution

  • It may not look like it at first glance, but t2 is a function prototype, not a variable declaration (search for "most vexing parse").

    You can change it to a variable by adding some parentheses:

    ThreadJoinRAII t2((std::thread(threadFunction)));
    

    then it will call your ThreadJoinRAII constructor.