2

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.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
dhu
  • 718
  • 6
  • 19
  • 1
    Unrelated to your problem but you should mark single argument constructors as `explicit` – Alan Birtles Dec 03 '20 at 06:47
  • @AlanBirtles Is this a general recommendation for that? I know I have ran into situation where explicit saved me. For example when I define `operator bool() const` functions. – dhu Dec 03 '20 at 23:04
  • Yes, it's not always necessary but doesn't usually do any harm so is a good habit to get into. A RAII type class is especially critical as an accidental conversion will result in the contained class getting destroyed – Alan Birtles Dec 04 '20 at 07:10

1 Answers1

6

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.

1201ProgramAlarm
  • 32,384
  • 7
  • 42
  • 56
  • 3
    Or use the curly brace version: `ThreadJoinRAII t2{std::thread(threadFunction)};` – Martin York Dec 03 '20 at 06:40
  • Thank you. So this is the example from wikipedia `TimeKeeper time_keeper(Timer());` and it is interrupted as `(void -> Timer) -> TimeKeeper` which makes sense. So in this case, I guess it is `(threadFunction -> std::thread) -> ThreadJoinRAII`. How can `threadFunction` be a type? – dhu Dec 03 '20 at 21:21
  • @dhu `threadFunction` isn't a type. It is the parameter name (just like in `ThreadJoinRAII t2(std::thread threadFunction);`). The wiki example does not have a parameter name. – 1201ProgramAlarm Dec 03 '20 at 21:32
  • @1201ProgramAlarm So in this case what is the type of the parameter name `threadFunction`? I believe I ran into a most vexing parse case but I don't think I fully understood why and how. – dhu Dec 03 '20 at 23:02
  • @1201ProgramAlarm I guess I misunderstood your statement. I think you meant `t2` is of type `std::thread -> ThreadJoinRAII`. But that's even more confusing to me. What If I had `ThreadJoinRAII t2(std::thread(int))`. I guess now I have `(int -> std::thread) -> ThreadJoinRAII`? So since `threadFunction` is not a unknown type, it is a parameter name? – dhu Dec 03 '20 at 23:15
  • 1
    @dhu `threadFunction` is of type `std::thread`. This is just like any function prototype or declaration: the parameter declaration consists of a type an optional parameter name. – 1201ProgramAlarm Dec 03 '20 at 23:30