std::thread and std::vector confusion?
Chris Katko
#SelectExpand
1 2void task1(std::string msg) 3{ 4 printf("task1 says: %s\n", msg.c_str()); 5} 6 7void main() 8 { 9 std::vector<std::thread> threads; 10 11 for(int i = 0; i < 5; ++i) 12 { 13 std::thread t1(task1, "Hello"); 14 threads.push_back(t1); //does not COMPILE 15 16 threads.push_back(std::thread(task1, "Hello")); //WORKS 17 } 18 printf("Started.\n"); 19 20 for(auto& t : threads) 21 { 22 t.join(); 23 } 24 25 printf("Finished.\n"); 26 }

Any ideas?

Also, I thought you had to "join" the thread to actually run it yet oddly enough they seem to run before join (they print before it prints "Started.") and I also thought join meant "pause main, run task until completion and then continue here." which would also imply they should be after started, and in order.

[edit]

It should be noted I'm using GCC 4.9.2 on Linux which may have faulty support for std::threads. I learned that the damn hard way after their regex was completely broken while I tried to learn it.

Edgar Reynaldo

I am making a wild guess here but it may be because the first std::thread is created on the stack and has to be copied but the second thread is a temporary object. It may be using move semantics in the push_back function when there is an rvalue instead of an lvalue.

Chris Katko

Please see my note, I posted before I fixed it. The first one^H^H^HLINE doesn't compile with a huge template error about allocater.

#SelectExpand
1In file included from /usr/include/x86_64-linux-gnu/c++/4.9/bits/c++allocator.h:33:0, 2 from /usr/include/c++/4.9/bits/allocator.h:46, 3 from /usr/include/c++/4.9/string:41, 4 from main.cpp:4: 5/usr/include/c++/4.9/ext/new_allocator.h: In instantiation of void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = std::thread; _Args = {const std::thread&}; _Tp = std::thread]: 6/usr/include/c++/4.9/bits/alloc_traits.h:253:4: required from static std::_Require<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type> std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::thread; _Args = {const std::thread&}; _Alloc = std::allocator<std::thread>; std::_Require<typename std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::type> = void] 7/usr/include/c++/4.9/bits/alloc_traits.h:399:57: required from static decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = std::thread; _Args = {const std::thread&}; _Alloc = std::allocator<std::thread>; decltype (_S_construct(__a, __p, (forward<_Args>)(std::allocator_traits::construct::__args)...)) = <type error>] 8/usr/include/c++/4.9/bits/stl_vector.h:918:34: required from void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::thread; _Alloc = std::allocator<std::thread>; std::vector<_Tp, _Alloc>::value_type = std::thread] 9main.cpp:33:23: required from here 10/usr/include/c++/4.9/ext/new_allocator.h:120:4: error: use of deleted function std::thread::thread(const std::thread&) 11 { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); } 12 ^ 13In file included from main.cpp:5:0: 14/usr/include/c++/4.9/thread:126:5: note: declared here 15 thread(const thread&) = delete; 16 ^

And per my thoughts, this SO post confirms what join does. But I'll look into your comments.

I'm not used to people replying so fast! ;D

Edgar Reynaldo

You don't have to join a thread to run, but you do have to start it (in this case perhaps automatically). If the threads have already finished though, join won't have any effect.

SiegeLord

Yeah, it's what Edgar said. The copy constructor of std::thread is deleted (as it makes no sense to copy a thread, I suppose), so you need to move the variable using std::move. I.e. you'd do threads.push_back(std::move(t1));.

Thomas Fjellstrom

I suppose it's helpful that c++11 does that move stuff, but I'd still avoid storing a static std::thread in a container. I generally store non trivial objects in std containers as pointers.

SiegeLord

The idiomatic pointer type for this purpose would be std::unique_ptr which also needs to be moved ;). These days I basically never use pointers unless I'm looking for some dynamic polymorphism.

beoran

Why not use Allegro's threads in stead?
https://www.allegro.cc/manual/5/ALLEGRO_THREAD

Thomas Fjellstrom
SiegeLord said:

The idiomatic pointer type for this purpose would be std::unique_ptr which also needs to be moved ;). These days I basically never use pointers unless I'm looking for some dynamic polymorphism.

Eh. I also tend not to mess with std::unique_ptr much either. ;)

l j

It might also be useful to note that moving a variable will destroy the original and should not be used afterwards, much like in Rust, except Rust enforces it and C++ should but doesn't.

Afaik, all std::threads must end before they go out of scope, so you should call thread::join before they get destroyed.

for(int i = 0; i < 5; ++i)
    {
    std::thread t1(task1, "Hello");
    threads.push_back(t1); // Tries to invoke the deleted copy constructor.
        
    threads.push_back(std::thread(task1, "Hello")); // Returns a rvalue.
        // Gets moved implicitly, you might want to use emplace_back to construct the thread directly in the vector and avoid any unnecessary copies.
    std::thread t2(task1, "Hello");
    threads.push_back(std::move(t2)); // Explicitly move t2
        // t2 should be considered 'consumed' and no longer be used.
    }

Took me a little while to figure these new move semantics out, but once you understand them they'll seem very elegant, at least that was my experience.

Kitty Cat
taronĀ  said:

It might also be useful to note that moving a variable will destroy the original and should not be used afterwards

IIRC, moving a variable puts the original into a cleared/destructible state. The object is destructed at the end of its scope as normal, so it is still valid after a move. What its actual state is, though, is left undefined (you may or may not be able to do anything with it except let it destruct).

l j

I realize my use of destroy might have been a little unclear, especially when I've used it with 2 different implied meanings.

But what I meant with the first instance of destroy is that it effectively can end up destroying the contents of the original and leaves it in an unspecified state, not that it calls the destructor, so you aren't supposed to use it in that state. Apparently an object you move from is called an xvalue (expiring value) now.

Chris Katko

I guess I understand std::move now. std::forward is confusing.

But geez. Everything keeps getting more and more complex. I mean... std::namespace names for actual language semantics now?

I mean, what's next, std::addition? I was going to joke and say std::cast, std::reference, std::pointer or std::const, but those actually exist with slight variations in spelling.

Which means you have to use the std::namespace to use the most basic functionality of C++. I guess that's not automatically a "bad" thing if you're "all in" for C++, but it calls into question the philosophy of "only pay for what you use" if you're including more and more complex parts of the STL just to run a program.

SiegeLord

Nothing stops you from just doing what std::move does manually:

threads.push_back((std::thread&&)t1);

Chris Katko

Okay, I think I finally get what join means.

A std::thread will start execution the moment you create it. However, if you want to pause the calling thread until a child thread is finished, you would call join to ensure execution of the parent doesn't continue until after the child finishes.

That is, if I have asset_manager.load_data() dispatching a bunch of worker tasks representing the loading of game assets, I certainly don't want to have asset_manager.load_data() returning or making decisions until that data has finished loading.

So when I'm playing around with tiny functions, they're very likely to finish before main() even makes a decision about them.

Peter Hull

Apologies if you already know this, but you can put

using namespace std;

just under your #includes to bring everything from std:: into scope (so you can put vector instead of std::vector) (link) - but bear in mind your definitions might clash with something from std::. You can also do it on a name-by-name basis, e.g

using std::vector;
using std::thread;

And apologies again for thread hijack, but when iterating

for(auto& t : threads) 
{  
    t.join();  
}

I'm sure I saw somewhere it should be auto&& but I don't know why or what this means. Any ideas?

Pete

Thomas Fjellstrom

auto detects the type from the rhs, and && tells it to be an rvalue reference. Which can let you take a reference of a rvalue, or temporary, it's used in implementing move semantics and such.

SiegeLord

You don't want an auto&& in a for loop unless you're planning on moving out those elements.

beoran

No really, what kind of an insane, cryptic mess is this all? :o

I really can't see how the C++ way with all those problems is easier than just a C array with malloc, realloc, and pointers to Allegro threads...

SiegeLord

The only reason C++ needs this nonsense is because it has overloadable assignment operator. For C, you could say it moves by default!

beoran

Yeah, I see, now that you mention it... That's why most languages that allow operator overloading still don' t allow the assignment operator to be overloaded. Ruby comes to mind as such a language...

And yes, C assignment copies the data by default, which is the correct thing to do for a low level language. :)

Chris Katko

Every time I think I'm getting a grasp on modern C++, it opens up a new steaming pile of complexity. :o

Thomas Fjellstrom

I've still not looked deep into move semantics. So far haven't needed it ;)

Peter Hull
SiegeLord said:

You don't want an auto&& in a for loop unless you're planning on moving out those elements.

I think maybe it moves only if you give it an rvalue, otherwise it collapses `&&` to `&` and just takes a reference. Have a look at
http://en.cppreference.com/w/cpp/language/range-for
and
http://stackoverflow.com/questions/9162808/what-does-auto-do

beoran said:

No really, what kind of an insane, cryptic mess is this all?

Indeed.

If you look into `<memory>` or any of the standard headers it's all very complicated stuff - unfortunately for C++ (IMO) you do need to understand a little of how it works to use it efficiently - you can't just leave it all to the library writers.

I've still not looked deep into move semantics. So far haven't needed it

I mostly use it for things like

MyClass inst;
//... do something with inst ...
myvector.push_back(std::move(inst));

without the move it'll make a copy of inst to put into the vector, then inst itself will be destroyed some time later. If inst is big/complicated that could be expensive.

l j

I personally don't use auto&& outside of template or generic lambdas.

Thomas Fjellstrom

without the move it'll make a copy of inst to put into the vector, then inst itself will be destroyed some time later. If inst is big/complicated that could be expensive.

Ok, makes sense. Mind you I typically use pointers for big complex objects. So it's kindof a moot point.

taronĀ  said:

I personally don't use auto&& outside of template or generic lambdas.

I tend to only use auto (forget about &&) when I want to avoid typing out a long string of line noise that they call container iterator types. In that case you don't even want to make it a reference.

Now I have used T&& a fair bit in templates. or "Args&&... args" but thinking about it, that's probably best to trim down to "Args&... args" or drop the reference all together possibly. I'll have to test it. If the compiler isn't collapsing && down to & there, it'll case moves in places it won't be wanted in that code, sooo bad idea :o

append: Basically I stole some code from here and modified it a bit.

Thread #615409. Printed from Allegro.cc