I want to implement a small thread wrapper that provides the information if a thread is still active, or if the thread has finished its work. For this I need to pass the functio
In the error message, you can see the difference void (*)()
vs void (&)()
. That's because std::thread's constructor parameters are std::decay
ed.
Add also std::ref
to f
:
template< class Function, class... Args>
ManagedThread::ManagedThread( Function&& f, Args&&... args):
mActive( false),
mThread( threadFunction< Function, Args...>, std::ref(mActive), std::ref(f), std::forward<Args>(args)...)
{
}
The following is simple, elegant, and to my knowledge the most-correct of all current answers (see below):
template < class Function, class... Args >
ManagedThread::ManagedThread( Function&& fn, Args&&... args ) :
mActive(false),
mThread(
[this]( auto&& fn2, auto&&... args2 ) -> void {
mActive = true;
fn2(std::forward<Args>(args2)...);
mActive = false;
},
std::forward<Function>(fn), std::forward<Args>(args)...
)
{}
The answer by DeiDei has a subtle but important flaw: the lambda takes the stack variables by reference, so if the thread starts, and tries to use, the stack variables after the constructor returns, you can get a stack-use-after-free error. Indeed, compiling with -fsanitize=address
is typically sufficient to demonstrate the problem.
The answer by O'Neill does not work when e.g. any arguments are lvalues, and is a bit clunky.
The answer by jotik is close, but does not work when the arguments are lvalues either, nor if they are member functions.
The answer by @O'Neil is correct, but I would like to offer a simple lambda approach, since you've tagged this as C++14
.
template<class Function, class... Args>
ManagedThread::ManagedThread(Function&& f, Args&&... args):
mActive(false),
mThread([&] /*()*/ { // uncomment if C++11 compatibility needed
mActive = true;
std::forward<Function>(f)(std::forward<Args>(args)...);
mActive = false;
})
{}
This would eliminate the need for an external function all together.
O'Neil and DeiDei got here first, and they're correct as far as I can tell. However, I'm still posting my solution to your problem.
Here's something that would work better:
#include <atomic>
#include <thread>
#include <utility>
class ManagedThread {
public: /* Methods: */
template <class F, class ... Args>
explicit ManagedThread(F && f, Args && ... args)
: m_thread(
[func=std::forward<F>(f), flag=&m_active](Args && ... args)
noexcept(noexcept(f(std::forward<Args>(args)...)))
{
func(std::forward<Args>(args)...);
flag->store(false, std::memory_order_release);
},
std::forward<Args>(args)...)
{}
bool isActive() const noexcept
{ return m_active.load(std::memory_order_acquire); }
private: /* Fields: */
std::atomic<bool> m_active{true};
std::thread m_thread;
};
It makes use of lambdas instead, and correctly uses std::atomic<bool>
instead of volatile
to synchronize the state, and also includes the appropriate noexcept()
specifiers.
Also note, that the underlying std::thread
is not joined or detached properly before destruction, hence leading to std::terminate()
being called.
I rewrote the test code as well:
#include <chrono>
#include <iostream>
int main() {
ManagedThread mt1(
[]() noexcept
{ std::this_thread::sleep_for(std::chrono::milliseconds(500)); });
std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive()
<< std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "thread 1 active = " << std::boolalpha << mt1.isActive()
<< std::endl;
}