Reputation: 185
I have some class-warper for std::thread. Here is the constructor:
template <typename Function, typename... Args>
InterruptibleThread(Function&& fun, Args&&... args)
{
_thread = std::thread([](std::atomic_bool * f, Function&& function, Args&&... arguments)
{
_flag_ref = f;
(function)(std::forward<Args>(arguments)...);
},
&_flag,
std::forward<Function>(fun)
, std::forward<Args>(args)...
);
}
And then I'm using it so (example): InterruptibleThread(&SourceImageList::StartFrameProcessingStatic, this, std::ref(it))
The compiler builds this code successfuly. But now I would like to make a vector of such objects:
std::vector<InterruptibleThread> grp;
I would like to allocate it on the stack so what I'm doing is:
grp.emplace_back(&SourceImageList::StartFrameProcessingStatic, this, std::ref(it));
And I'm getting this error:
C2064 term does not evaluate to a function taking 0 arguments
Here are the options which are validated by the compiler:
1) grp.push_back(new InterruptibleThread(&SourceImageList::StartFrameProcessingStatic, this, std::ref(it)));
2) grp.push_back(InterruptibleThread(&SourceImageList::StartFrameProcessingStatic, this, std::ref(it)));
But the first one is allocating an object on the heap so I need to release it manually, and the second one makes a copy of the object.
Can I use emplace_back
here (compiler is MSVC 2015 update 3)?
Update
Ok, I made some fixes based on answers. Here is the final version of this class:
#pragma once
#include <exception>
#include <atomic>
#include <thread>
#include <future>
#include <windows.h>
// Synopsis
class InterruptThreadException;
class InterruptibleThread;
// Interrupt exception
class InterruptThreadException : public virtual std::exception {
public:
virtual char const* what() const override { return "interrupt"; }
}; // class InterruptThreadException
// Interruptible thread
class InterruptibleThread {
public:
static void InterruptionPoint() noexcept(false) {
if (!InterruptibleThread::_flag_ref) { return; }
if (!InterruptibleThread::_flag_ref->load()) { return; }
throw InterruptThreadException();
} // check_for_interrupt
template <typename Function>
InterruptibleThread(Function&& fun) :
_thread([this, fun = std::move(std::forward<Function>(fun))]
{
_flag_ref = _flag.get();
fun();
})
{}
InterruptibleThread(InterruptibleThread&&) = default;
InterruptibleThread(const InterruptibleThread&) = delete;
bool Interrupting() const { return _flag->load(); }
void Interrupt() { _flag->store(true); }
void Join()
{
_thread.join();
}
bool TimedJoin(int msec)
{
return (std::async([=]() {Join(); }).wait_for(std::chrono::milliseconds(msec)) != std::future_status::timeout);
}
bool Joinable()
{
return _thread.joinable();
}
void Terminate()
{
TerminateThread(_thread.native_handle(), -1);
}
~InterruptibleThread()
{
if (_flag.get() != nullptr)
{
*_flag = false;
Interrupt();
}
if (_thread.joinable())
_thread.join()
}
private:
static thread_local std::atomic_bool* _flag_ref;
std::unique_ptr<std::atomic_bool> _flag = std::make_unique<std::atomic_bool>();
std::thread _thread;
};
And example of use:
std::vector<InterruptibleThread> grp;
for (auto it : _sourceImages)
grp.emplace_back([this, it] {
it->StartFrameProcessing();
it->SetImageDelay(const_cast<EngineConfig*>(GetConfig())->ImageDelay);
});
Upvotes: 1
Views: 2083
Reputation: 85351
You could modernize your code and pass a lambda into InterruptibleThread
instead of passing a function and it's arguments (i.e. bind-style).
#include <atomic>
#include <iostream>
#include <thread>
#include <vector>
struct InterruptibleThread
{
std::thread _thread;
template <typename Function>
InterruptibleThread(Function&& fun)
: _thread(std::forward<Function>(fun))
{
}
};
struct Test
{
std::vector<InterruptibleThread> grp;
void test(int x) {
grp.emplace_back([this, x]{ t1(x); }); // <==== HERE
}
void t1(int x) {
std::cout << x << "\n";
}
};
int main()
{
Test t;
t.test(5);
t.grp[0]._thread.join();
}
Upvotes: 5
Reputation: 3103
My guess would be that the immediate problem is that the vector class is trying to instantiate the move constructor of your class, which happens to match the template signature but then fails to compile the body with Function = InterruptibleThread, Args = {}
. You'll need to provide an override by explicitly defaulting the move constructor.
Aside from this, a couple other points:
std::reference_wrapper
arguments. (It didn't work under gcc, and I don't see how it could have worked in MSVC.)atomic_bool
. That would prevent a default move constructor from being able to be instantiated.Here's a revised version which compiles for me under gcc:
#include <thread>
#include <mutex>
#include <atomic>
#include <vector>
#include <utility>
thread_local std::atomic_bool* _flag_ref;
class InterruptibleThread {
private:
std::thread _thread;
// Need unique_ptr instead of a directly contained atomic_bool
// to make the object MoveConstructible.
std::unique_ptr<std::atomic_bool> _flag;
public:
template <typename Function, typename... Args>
explicit InterruptibleThread(Function&& fun, Args&&... args)
{
_flag = std::make_unique<std::atomic_bool>();
// Use std::bind to take care of all the details of
// calling pointer-to-member-function or pointer-to-member-variable,
// unwrapping std::reference_wrapper arguments, etc.
auto bound_fun = std::bind(std::forward<Function>(fun), std::forward<Args>(args)...);
_thread = std::thread([this, bound_fun = std::move(bound_fun)]
{
_flag_ref = _flag.get();
bound_fun();
}
);
}
InterruptibleThread(InterruptibleThread&&) = default;
InterruptibleThread(const InterruptibleThread&) = delete;
};
class Foo {
public:
void func(int& n);
};
void test_func() {
std::vector<InterruptibleThread> v;
Foo f;
int n = 5;
v.emplace_back(&Foo::func, &f, std::ref(n));
}
Upvotes: 2