0

My code looks like the following:

#include <list>
#include <thread>

void my_function(int val) {
    // Empty function
}

int main() {
    std::list<std::thread> threads;
    for (int i = 0 ; i < 10 ; i++) {
        threads.push_back(std::thread(my_function, i));
    }

    return 0;
}

The fact that I use threads.push_back() means that I run the copy-constructor std::thread::thread(const thread&).

  • Is it safe?

  • Should I use std::move ?

Please suppose that I don't know in advance how many threads I am going to need, so replacing the list by an array or by an std::vector, is not an option for me (std::vector would be an option only if I knew the number of threads in advance, as I cannot afford the vector's realloc operations).

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
SomethingSomething
  • 11,491
  • 17
  • 68
  • 126
  • "_The fact that I use `threads.push_back()` means that I run the copy-constructor `std::thread::thread(const thread&)`._" Such constructor is [`delete`d](https://en.cppreference.com/w/cpp/thread/thread/thread), so it's impossible for you to run it. – Algirdas Preidžius Oct 10 '18 at 14:13
  • 3
    Why not use `emplace_back`? It sounds like what you're after. https://en.cppreference.com/w/cpp/container/list/emplace_back – Cinder Biscuits Oct 10 '18 at 14:15
  • 1
    "Should I use std::move" no, temporary is already an rvalue, you need to use `std::move` to cast named variable. – Slava Oct 10 '18 at 14:16
  • So `emplace_back` will function as the constructor of the list template, and will thus make sure that the object is constructed directly on the list? – SomethingSomething Oct 10 '18 at 14:17
  • @Slava you're saying that even if the copy constructor was not "deleted", it would not be called anyway, as the "temporary" thread is an rvalue? – SomethingSomething Oct 10 '18 at 14:20
  • Yep, with `emplace_back` the object gets constructed at the location provided by the list. Just as if you had called placement new at that address. – Cinder Biscuits Oct 10 '18 at 14:21
  • 2
    @SomethingSomething `std::thread(my_function, i)` is an rvalue. `std::vector::push_back` has an overload for rvalues which uses move semantics and makes no copies. – François Andrieux Oct 10 '18 at 14:21
  • "it would not be called anyway", not quite that. It would prefer move ctor or assignment operator if such one exists. – Slava Oct 10 '18 at 14:22
  • I am learning here new things today. Just to be sure - the fact that the thread constructor is not assigned to any variable, defines it as an rvalue? Which means it is going to be accepted by a method that gets `std::thread&&` and be constructed only when that function is called? – SomethingSomething Oct 10 '18 at 14:24
  • "the fact that the thread constructor is not assigned to any variable" you should use proper terminology. You do not assign constructor to something, you create an instance of `std::thread` class, what type that instance has depends on expression. – Slava Oct 10 '18 at 14:28
  • 1
    @SomethingSomething - Almost. It's constructed at the call site so that the reference could bind to it. Then, inside the list node, another thread is constructed by *moving* the contents of the one at the call site. The move is safe. There is only ever a single handle that owns the thread. And I must second Slava, you should get a refresher book on C++11 to catch up on terminology. – StoryTeller - Unslander Monica Oct 10 '18 at 14:28
  • @StoryTeller or that move could be eliminated by optimizer and it would be constructed in place. But it does not matter in this case - result will be the same. – Slava Oct 10 '18 at 14:30
  • @Slava - Only if the move constructor has no observable behavior and the push back is inlined. Otherwise the core language itself requires two instances. – StoryTeller - Unslander Monica Oct 10 '18 at 14:31
  • @StoryTeller `push_back()` is a template, so it can be inlined easily. For side effect, I think compiler allowed to omit copy ctor even if there are side effects. It is not the same for move ctor? – Slava Oct 10 '18 at 14:33
  • @Slava - `push_back` is a black box. Depending on the machinery it invokes it can either be inlined easily or not. Ans since I cannot predict it reliably I would not wish to lead someone astray by pretending I can. And if you think about guaranteed copy elision, it doesn't hold when passing by function arguments by reference. The reference must bind to something. It's not even entirely reliable when passing by value. – StoryTeller - Unslander Monica Oct 10 '18 at 14:36
  • " if you think about guaranteed copy elision" no I do not, even before guaranteed copy elision compiler was allowed to omit copy ctor even if it has side effects. And I am not saying if it will elimininate for sure or not, I just said it is possible that move ctor would be eliminated here. – Slava Oct 10 '18 at 14:42
  • Does the fact that my threads are "moved" to the list, which means they are going to be copied to the heap, means that I will need to manually iterate on the list and delete each of them? – SomethingSomething Oct 10 '18 at 14:46
  • @Slava - "Side-effects" is not what I meant by observable behavior. I mean what the C++ standard means, and it doesn't allow eliminating it, period. – StoryTeller - Unslander Monica Oct 10 '18 at 14:48
  • @SomethingSomething - No. – StoryTeller - Unslander Monica Oct 10 '18 at 14:48
  • @StoryTeller looks like you are wrong https://stackoverflow.com/questions/13099603/c11-move-constructor-not-called-default-constructor-preferred – Slava Oct 10 '18 at 15:22
  • @Slava - Looks like you just didn't read any of the answers. Nothing there applies to the case of forwarding function arguments. – StoryTeller - Unslander Monica Oct 10 '18 at 15:25
  • If the two constructors are implemented - both copy constructor and move constructor (unlike in std::thread, where the copy constructor is deleted), which one will be called if the passed item is "temporary", like in the example? – SomethingSomething Oct 11 '18 at 08:30

1 Answers1

6

The fact that I use threads.push_back() means that I run the copy-constructor

No, it does not. Since C++11 push_back is overloaded to accept an rvalue reference to the list's value type.

You cannot run std::thread's copy constructor, since it's declared as deleted. The above mentioned overload of push_back was added for this exact purpose of supporting move only types, like a thread handle.

If you want to initialize the thread directly into the container without a move, then emplace_back can do that. But you need to pass the parameters for the std::thread constructor in, not a thread that is initialized from them:

threads.emplace_back(my_function, i);
StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 1
    Does it mean that the code is safe? Just as if I assigned the thread directly to the placeholder in the list? – SomethingSomething Oct 10 '18 at 14:15
  • @SomethingSomething - Yes, as far as the thread creation itself goes it's safe. The standard library deletes the copy constructor so that unsafe creation won't even compile. And the move constructor does the sane thing. It aims to be correct by construction. – StoryTeller - Unslander Monica Oct 10 '18 at 14:16