6

According to cppreference.com, the std::thread constructor with no parameter means:

Creates new thread object which does not represent a thread.

My questions are:

  1. Why do we need this constructor? And if we create a thread using this constructor, how can we "assign" a thread function later?
  2. Why don't we have a "run(function_address)" method so that when constructed with no parameter, we can specify a function to "run" for that thread.
  3. Or, we can construct a thread with a callable parameter (function, functors, etc.) but call a "run()" method to actually execute the thread later. Why is std::thread not designed in this way?
Niall
  • 30,036
  • 10
  • 99
  • 142
james
  • 1,107
  • 14
  • 29

4 Answers4

13

Your question suggests there might be some confusion and it would be helpful to clearly separate the ideas of a thread of execution from the std::thread type, and to separate both from the idea of a "thread function".

  • A thread of execution represents a flow of control through your program, probably corresponding to an OS thread managed by the kernel.
  • An object of the type std::thread can be associated with a thread of execution, or it can be "empty" and not refer to any thread of execution.
  • There is no such concept as a "thread function" in standard C++. Any function can be run in a new thread of execution by passing it to the constructor of a std::thread object.
  1. why do we need this constructor?

To construct the empty state that doesn't refer to a thread of execution. You might want to have a member variable of a class that is a std::thread, but not want to associate it with a thread of execution right away. So you default construct it, and then later launch a new thread of execution and associate it with the std::thread member variable. Or you might want to do:

std::thread t;
if (some_condition) {
  t = std::thread{ func1, arg1 };
}
else {
  auto result = some_calculation();
  t = std::thread{ func2, arg2, result };
}

The default constructor allows the object t to be created without launching a new thread of execution until needed.

And if we create a thread using this constructor, how can we "assign" a thread function later?

You "assign" using "assignment" :-)

But you don't assign a "thread function" to it, that is not what std::thread is for. You assign another std::thread to it:

std::thread t;
std::thread t2{ func, args };
t = std::move(t2);

Think in terms of creating a new thread of execution not "assigning a thread function" to something. You're not just assigning a function, that's what std::function would be used for. You are requesting the runtime to create a new thread of execution, which will be managed by a std::thread object.

  1. Why don't we have a "run(function_address)" method so that when constructed with no parameter, we can specify a function to "run" for that thread.

Because you don't need it. You start new threads of execution by constructing a std::thread object with arguments. If you want that thread of execution to be associated with an existing object then you can do that by move-assigning or swapping.

  1. Or, we can construct a thread with a callable parameter(function, functors, etc.) but call a "run()" method to actually execute the thread later. Why std::thread is not designed in this way?

Why should it be designed that way?

The std::thread type is for managing a thread of execution not holding a callable object for later use. If you want to create a callable object that can be later run on a new thread of execution there are lots of ways to do that in C++ (using a lambda expression, or std::bind, or std::function, or std::packaged_task, or a custom functor type). The job of std::thread is to manage a thread of execution not to hold onto a callable object until you want to call it.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
4

The default constructor is provided such that an "empty" thread object can be created. Not all thread objects will be associated with a thread of execution at the time of construction of said object. Consider when the thread is a member of some type and that type has a default constructor. Consider another case that the thread type has no concept of a "suspended" thread, i.e. it can't be created in a suspended state.

The thread type doesn't have a "run" method of some sort since the one of the original design decisions (IIRC) was to have a "strong" association between the thread object and the thread of execution. Allowing threads to be "moved" makes that intent clearer (in my opinion). Hence moving an instance of a thread object to an "empty" object is clearer than attempting to "run" a thread.

It is conceivable that you can create a wrapper class of some sort that offers the "run" method, but I think this may be a narrower use case, and that can be solved given the API of the std::thread class.

Niall
  • 30,036
  • 10
  • 99
  • 142
  • thanks, @Niall, but why the original design favors a "strong" association in the first place? What's the benefit of it? – james Jan 27 '16 at 12:56
  • A lot of the discussion at the time was around what to do with an "unjoined" thread once the object goes out of scope? Given that there was no real consensus on what to do, the result as that `std::terminate` be called since that provided the "safest" outcome. I.e. if the programmer intended it to be detached form the running thread, then they need to be explicit on this, and a "join" in the destructor could lead to unintended deadlocks. – Niall Jan 27 '16 at 13:00
2

EDIT: Maybe lets firsts comment on the very last part:

3.2 Why std::thread is not designed in this way?

I don't know why exactly (there are surely advantages and disatvantages - see Jonathan Wakely's answer for a more details about the rational behind it), but it seems that c++11 std::thread is modelled much closer to pthreads than e.g. QT's or Java's QThread/Thread classes, which might be the source of your confusion.

As to the rest of your questions:

1.1 why do we need this constructor?

You might want to create a std::thread variable but don't directly start a thread (e.g. a class member variable or an element of a static array, es shown by alangab). It's not much different to an std::fstream that can be created without a filename.

1.2 And if we create a thread using this constructor, how can we "assign" a thread function later?

For example:

std::thread myThread;

// some other code

myThread = std::thread(foo()); 
  1. Why don't we have a "run(function_address)" method so that when constructed with no parameter, we can specify a function to "run" for that thread.

I don't know, why it was designed like this, but I don't see the benefit a run method would have compared to above syntax.

3.1 Or, we can construct a thread with a callable parameter(function, functors, etc.) but call a "run()" method to actually execute the thread later.

You can simulate this by creating a lambda or std::function object and create the thread when you want to run the function.

auto myLambda = [=]{foo(param1, param2);};
// some other code
std::thread myThread(myLambda);

If you want to use the syntax you describe, I'd recommend to write your own Thread wrapper class (should only take a few dozen lines of code) that (optionally) also ensures that the thread is either detached or joined upon destruction of the wrapper, which is - in my opinion - the main problem with std::thread.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
2

The default constructor gives you then possibility to create array of threads:

thread my_threads[4];

for (int i=0; i<4; i++)
{
   thread temp(func,...);
   my_threads[i]=move(temp);
}

the thread created with default costructor "become" a "real" thread with the move costructor.

You can use thread with standard container if you need/like.

alangab
  • 849
  • 5
  • 20