As stated in an answer to this question, std::invoke
handles calling not just of simple functions but also other callable types.
Unfortunately I am currently constrained to C++14 - so does anyone know of a C++14 compatible alternative?
My motivation:
My actual problem is that I am trying to write a simple wrapper for std::thread
where I want to wrap the call of the passed function f
with info like isRunning
, which will be set to true
before calling f();
and false
afterwards.
One possible way I found, was to pass a member-method bound via std::bind(&Class::method, classInstance)
- this however breaks the API, as this is something the user of my Wrapper class would have to do.
In case anyone is interested, here is my code:
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <memory>
#include <mutex>
#include <thread>
class ThreadPeriodic : public std::thread
{
protected:
struct Settings
{
Settings(std::chrono::nanoseconds const periodDuration)
: isRunning(false)
, stopped(false)
, periodDuration(periodDuration)
{
}
volatile std::atomic<bool> isRunning;
std::mutex mutexStop;
std::condition_variable conditionVariableStop;
volatile std::atomic<bool> stopped;
std::chrono::nanoseconds const periodDuration;
};
std::shared_ptr<Settings> settings_;
template< class Function, class... Args >
class WrapperClass_
{
WrapperClass_() = delete;
public:
// https://stackoverflow.com/questions/34731367/how-to-pass-variadic-args-to-a-stdthread
static void wrapperMethod(std::shared_ptr<Settings> settings,
typename std::decay<Function>::type&& f,
typename std::decay<Args>::type&&... args)
{
settings->isRunning = true;
std::chrono::steady_clock::time_point nextPeriod = std::chrono::steady_clock::now();
bool stopped = settings->stopped.load();
while (!stopped)
{
try
{
f(std::forward<Args>(args)...);
}
catch (...)
{
// allthough this should never happen...
settings->isRunning = false;
throw;
}
nextPeriod += settings->periodDuration;
std::unique_lock<std::mutex> lock(settings->mutexStop);
stopped = settings->conditionVariableStop.wait_until(lock, nextPeriod, [settings](){return settings->stopped;});
}
settings->isRunning = false;
}
};
public:
ThreadPeriodic() noexcept
{
}
ThreadPeriodic(ThreadPeriodic && other) noexcept
{
operator=(std::move(other));
}
template< class Function, class... Args >
explicit ThreadPeriodic(std::chrono::nanoseconds const periodDuration, Function&& f, Args&&... args)
: settings_(std::make_shared<Settings>(periodDuration))
{
std::thread::operator=(std::thread(ThreadPeriodic::WrapperClass_<Function, Args...>::wrapperMethod,
settings_,
std::forward<Function>(f),
std::forward<Args>(args)...));
}
template< class Function, class... Args >
explicit ThreadPeriodic(Function&& f, Args&&... args)
: ThreadPeriodic(std::chrono::nanoseconds(0), std::forward<Function>(f), std::forward<Args>(args)...)
{
}
template< class Rep, class Period, class Function, class... Args >
explicit ThreadPeriodic(std::chrono::duration<Rep, Period> const periodDuration, Function&& f, Args&&... args)
: ThreadPeriodic(std::chrono::duration_cast<std::chrono::nanoseconds>(periodDuration), std::forward<Function>(f), std::forward<Args>(args)...)
{
}
ThreadPeriodic( const ThreadPeriodic& ) = delete;
ThreadPeriodic& operator=( ThreadPeriodic&& other ) noexcept
{
std::thread::operator=(std::move(other));
settings_ = std::move(other.settings_);
return *this;
}
bool isRunning() const
{
std::shared_ptr<Settings> settings = settings_;
return static_cast<bool>(settings) ? settings->isRunning.load() : false;
}
bool isStarted() const
{
std::shared_ptr<Settings> settings = settings_;
return static_cast<bool>(settings) && !settings->stopped;
}
bool isStopped() const
{
std::shared_ptr<Settings> settings = settings_;
return static_cast<bool>(settings) ? settings->stopped.load() : true;
}
// If joinNow is false, join() shall be called later on manually, as to ensure
// the thread is actually joined as it should.
void stop(bool const joinNow = true)
{
std::shared_ptr<Settings> settings = settings_;
if (!static_cast<bool>(settings))
{
throw std::logic_error("ThreadPeriodic::stop: this instance does not represent a thread.");
}
else if (settings->stopped)
{
throw std::logic_error("ThreadPeriodic::stop: this instance is already stopped.");
}
else
{
{
std::unique_lock<std::mutex> lock(settings->mutexStop);
settings->stopped = true;
}
settings->conditionVariableStop.notify_all();
if (joinNow && joinable())
{
join();
}
}
}
};
And if anyone wants to test it, here is a basic program:
#include <iostream>
class TestClass
{
public:
explicit TestClass(int const start = 0)
: start(start)
{
}
void printNumber(int const step = 1)
{
static int number = start;
std::cout << number << std::endl;
number += step;
}
int start;
};
int main()
{
TestClass testInstance(0);
// ThreadPeriodic thread(std::chrono::milliseconds(500), &TestClass::printNumber, testInstance, 3);
ThreadPeriodic thread(std::chrono::milliseconds(500), std::bind(&TestClass::printNumber, testInstance, std::placeholders::_1), 3);
std::this_thread::sleep_for(std::chrono::seconds(2));
thread.stop();
return 0;
}