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;
}
Related
I implemented a ThreadPool to test my knowledge of C++ concurrency. However, when I run the following code, it does not proceed and my mac becomes extremely slow and eventually does not respond—I check the monitor later and find the reason is that the kernel_task launches several clang processes and each runs nearly 100% CPU. I've carefully gone through the code several times, but still unable to locate the problem.
Here's the test code for ThreadPool. When I run this code, there is nothing printed on the terminal. Worse still, even if I cancel the process(via contrl+c), kernel_task creates several clang later and my computer crashes.
// test code for ThreadPool
#include <iostream>
#include <functional>
#include <future>
#include "thread_pool.hpp"
int task() {
static std::atomic<int> i = 1;
std::cout << i.fetch_add(1, std::memory_order_relaxed) << "task\n";
return i.load(std::memory_order_relaxed);
}
int main() {
ThreadPool<int()> thread_pool(1);
auto f1 = thread_pool.submit(task, false);
std::cout << "hello" << '\n';
auto f2 = thread_pool.submit(task, false);
std::cout << f1.get() << '\n';
std::cout << f2.get() << '\n';
}
Here's the definition of ThreadPool.
// thread_pool.hpp
#include <atomic>
#include <algorithm>
#include <chrono>
#include <functional>
#include <future>
#include <memory>
#include <queue>
#include <thread>
#include "queue.hpp"
template<typename Func>
class ThreadPool {
public:
ThreadPool(std::size_t=std::thread::hardware_concurrency()); // should I minus one here for the main thread?
~ThreadPool();
template<typename... Args,
typename ReturnType=typename std::result_of<std::decay_t<Func>(std::decay_t<Args>...)>::type>
std::future<ReturnType> submit(Func f, bool local=true);
private:
void worker_thread();
void run_task();
using LocalThreadType = std::queue<std::packaged_task<Func>>;
static thread_local LocalThreadType local_queue; // local queue, not used for now
using ThreadSafeQueue = LockBasedQueue<std::packaged_task<int()>,
std::list<std::packaged_task<int()>>>;
std::shared_ptr<ThreadSafeQueue> shared_queue;
std::atomic_bool done;
std::vector<std::thread> threads;
};
template<typename Func>
ThreadPool<Func>::ThreadPool(std::size_t n): done(false) {
threads.emplace_back(&ThreadPool::worker_thread, this);
}
template<typename Func>
ThreadPool<Func>::~ThreadPool() {
done.store(true, std::memory_order_relaxed);
for (auto& t: threads)
t.join();
}
template<typename Func>
template<typename...Args,
typename ReturnType>
std::future<ReturnType> ThreadPool<Func>::submit(Func f, bool local) {
auto result = local? post_task(f, local_queue):
post_task(f, *shared_queue);
return result;
}
template<typename Func>
void ThreadPool<Func>::run_task() {
if (!local_queue.empty()) {
auto task = std::move(local_queue.front());
local_queue.pop();
task();
}
else {
std::packaged_task<Func> task;
auto flag = shared_queue->try_pop(task);
if (flag)
task();
else {
using namespace std::chrono_literals;
std::this_thread::sleep_for(1s);
}
}
}
template<typename Func>
void ThreadPool<Func>::worker_thread() {
while (!done.load(std::memory_order_relaxed)) {
run_task();
}
}
template<typename Func>
thread_local typename ThreadPool<Func>::LocalThreadType ThreadPool<Func>::local_queue = {};
Here's the definition of post_task and LockBasedQueue, which have passed the test code in the next code block.
// queue.hpp
#include <mutex>
#include <list>
#include <deque>
#include <queue>
#include <memory>
#include <future>
#include <condition_variable>
template<typename T, typename Container>
class LockBasedQueue; // forward declaration
template<typename Func, typename... Args,
typename ReturnType=typename std::result_of<std::decay_t<Func>(std::decay_t<Args>...)>::type,
typename Container=std::list<Func>,
typename ThreadQueue=LockBasedQueue<std::packaged_task<ReturnType(Args...)>, Container>>
std::future<ReturnType> post_task(Func f, ThreadQueue& task_queue) {
std::packaged_task<ReturnType(Args...)> task(f);
std::future res = task.get_future();
task_queue.push(std::move(task)); // packaged_task is not copyable
return res;
}
// the general template is omitted and it's not needed in this context
template<typename T>
class LockBasedQueue<T, std::list<T>> {
public:
// constructors
LockBasedQueue(): head(std::make_unique<Node>()), tail(head.get()) {}
LockBasedQueue(LockBasedQueue&&);
// assignments
LockBasedQueue& operator=(LockBasedQueue&&);
// general purpose operations
void swap(LockBasedQueue&);
bool empty() const;
std::size_t size() const;
// queue operations
void push(const T&);
void push(T&&);
template <typename... Args>
void emplace(Args&&... args);
T pop();
bool try_pop(T&);
// delete front() and back(), these functions may waste notifications. To enable these function, one should replace notify_one() with notify_all() in push() and emplace()
T& front() = delete;
const T& front() const = delete;
T& back() = delete;
const T& back() const = delete;
private:
struct Node {
std::unique_ptr<T> data; // data is a pointer as it may be empty
std::unique_ptr<Node> next;
};
Node* get_tail() {
std::lock_guard l(tail_mutex);
return tail;
}
Node* get_head() {
std::lock_guard l(head_mutex);
return head.get();
}
std::unique_lock<std::mutex> get_head_lock() {
std::unique_lock l(head_mutex);
data_cond.wait(l, [this] { return head.get() != get_tail(); });
return l;
}
T pop_data() {
auto data = std::move(*head->data);
head = std::move(head->next); // we move head to the next so that the tail is always valid
return std::move(data);
}
std::unique_ptr<Node> head;
std::mutex head_mutex;
Node* tail;
std::mutex tail_mutex;
std::condition_variable data_cond;
};
template<typename T>
LockBasedQueue<T, std::list<T>>::LockBasedQueue(
LockBasedQueue<T, std::list<T>>&& other) {
{
std::scoped_lock l(head_mutex, other.head_mutex);
head(std::move(other.data_queue));
}
{
std::lock_guard l(tail_mutex);
tail = head.get();
}
{
std::lock_guard l(other.tail_mutex);
other.tail = nullptr;
}
}
template<typename T>
LockBasedQueue<T, std::list<T>>&
LockBasedQueue<T, std::list<T>>::operator=(
LockBasedQueue<T, std::list<T>>&& rhs) {
{
std::scoped_lock l(head_mutex, rhs.head_mutex);
head(std::move(rhs.data_queue));
}
{
std::lock_guard l(tail_mutex);
tail = head.get();
}
{
std::lock_guard l(rhs.tail_mutex);
rhs.tail = nullptr;
}
}
template<typename T>
void LockBasedQueue<T, std::list<T>>::swap(
LockBasedQueue<T, std::list<T>>& other) {
{
std::scoped_lock l(head_mutex, other.head_mutex);
head(std::move(other.data_queue));
}
{
std::lock_guard l(tail_mutex);
tail = head.get();
}
{
std::lock_guard l(other.tail_mutex);
other.tail = other.head.get();
}
}
template<typename T>
inline bool LockBasedQueue<T, std::list<T>>::empty() const {
return get_head() == get_tail();
}
template<typename T>
std::size_t LockBasedQueue<T, std::list<T>>::size() const {
int n = 0;
std::lock_guard l(tail_mutex); // do not use get_tail() here to avoid race condition
for (auto p = get_head(); p != tail; p = p->next.get())
++n;
return n;
}
template<typename T>
void LockBasedQueue<T, std::list<T>>::push(const T& data) {
push(T(data));
}
template<typename T>
void LockBasedQueue<T, std::list<T>>::push(T&& data) {
{
auto p = std::make_unique<Node>();
std::lock_guard l(tail_mutex);
tail->data = std::make_unique<T>(std::move(data)); // we add data to the current tail, this allows us to move head to the next when popping
tail->next = std::move(p);
tail = tail->next.get();
}
data_cond.notify_one();
}
template<typename T>
template<typename...Args>
void LockBasedQueue<T, std::list<T>>::emplace(Args&&... args) {
{
auto p = std::make_unique<Node>();
std::lock_guard l(tail_mutex);
tail->data = std::make_unique<T>(std::forward<Args>(args)...);
tail->next = std::move(p);
tail = tail->next.get();
}
data_cond.notify_one();
}
template<typename T>
T LockBasedQueue<T, std::list<T>>::pop() {
auto l(get_head_lock());
return pop_data();
}
template<typename T>
bool LockBasedQueue<T, std::list<T>>::try_pop(T& data) {
std::lock_guard l(head_mutex);
if (head.get() == get_tail())
return false;
data = pop_data();
return true;
}
Here's the code I used to test LockBasedQueue and post_task. The following test code works without any problem.
// test code for LockBasedQueue and post_task
#include <iostream>
#include <functional>
#include <future>
#include "queue.hpp"
LockBasedQueue<std::packaged_task<int()>, std::list<std::packaged_task<int()>>> task_queue; // thread safe queue, which handles locks inside
void task_execution_thread() {
bool x = true;
while (x) { // for debugging purpose, we only execute this loop once
auto task = task_queue.pop(); // Returns the front task and removes it from queue. Waits if task_queue is empty
task(); // execute task
x = false;
}
}
int task() {
static std::atomic<int> i = 1;
std::cout << i.fetch_add(1, std::memory_order_relaxed) << "task\n";
return i.load(std::memory_order_relaxed);
}
int main() {
std::thread t1(task_execution_thread);
std::thread t2(task_execution_thread);
auto f1 = post_task(task, task_queue);
auto f2 = post_task(task, task_queue);
std::cout << "f1: " << f1.get() << '\n';
std::cout << "f2: " << f2.get() << '\n';
t1.join();
t2.join();
}
I test the code using g++ -std=c++2a on the MacOS 11.2.3.
shared_queue is default initialised therefore calling methods on it is undefined behaviour. Initialising it in the constructor of ThreadPool:
ThreadPool<Func>::ThreadPool(std::size_t n) : done(false), shared_queue(std::make_shared<ThreadSafeQueue>()) {
makes your code work: https://godbolt.org/z/P9G1T5
My goal is to create a generic Event type that can be used in subscribe/notify architecture, but I am having trouble getting class member functions to work.
Event.hpp:
#ifndef EVENT_HPP
#define EVENT_HPP
#include <functional>
#include <unordered_set>
template <typename ... EventParameterTypes>
struct Event {
typedef void(*EventCallback)(EventParameterTypes ...);
template <typename ClassType>
Event &subscribe(ClassType *instance, void(ClassType::*eventCallback)(EventParameterTypes...)) {
auto bound = [=](EventParameterTypes&& ... params) { return ((instance)->*(eventCallback))(std::forward<EventParameterTypes>(params)...); };
return this->subscribe(bound);
}
Event &subscribe(EventCallback eventCallback) {
return this->addEventCallback(eventCallback);
}
void notify(EventParameterTypes ... types) {
for (const auto &it : this->m_eventCallbacks) {
if (it) (*it)(types...);
}
}
private:
std::unordered_set<EventCallback> m_eventCallbacks;
Event &addEventCallback(EventCallback eventCallback) {
auto foundIterator = std::find(this->m_eventCallbacks.begin(), this->m_eventCallbacks.end(), eventCallback);
if (foundIterator != this->m_eventCallbacks.end()) {
return *this;
}
this->m_eventCallbacks.insert(eventCallback);
return *this;
}
};
#endif //EVENT_HPP
Main.cpp:
#include "Event.hpp"
struct EventTest {
using MyEvent = Event<int>;
MyEvent myEvent;
};
void myEventCallback(int) {
//Stuff
}
struct EventListener {
void eventListenerCallback(int) {
//Stuff
}
};
int main() {
EventListener eventListener{};
EventTest eventTest{};
eventTest.myEvent.subscribe(&myEventCallback); //OK
eventTest.myEvent.subscribe(&eventListener, &EventListener::eventListenerCallback); //Compile error
}
Is there any way to resolve this? I have looked into std::bind but it only works with a certain amount of placeholders, and the lambda causes the function to be of a local lambda type.
You are hoping to somehow pack instance and eventCallback into a single function pointer. You can't do that, no more than you can pour an ocean into a thimble. One way out is to make EventCallback a typedef for a suitable specialication of std::function, as in
typedef std::function<void(EventParameterTypes ...)> EventCallback;
I won't be surprised if the rest of the code would just work after this, with no modifications.
Using #Igor Tandetnik s advice, I came up with the following (working) implementation:
Event.hpp
#ifndef EVENT_HPP
#define EVENT_HPP
#include <functional>
#include <functional>
#include <type_traits>
#include <vector>
template<typename T, typename... U>
size_t getFunctionAddress(std::function<T(U...)> f) {
typedef T(fnType)(U...);
fnType **fnPointer = f.template target<fnType *>();
if (fnPointer == nullptr) {
return 0;
}
return (size_t) *fnPointer;
}
template<typename ... EventParameterTypes>
struct Event {
template<class ClassType> using MemberPtr = void (ClassType::*)(EventParameterTypes...);
using EventCallback = std::function<void(EventParameterTypes ...)>;
template<class ClassType>
Event &subscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->operator+=(bound);
}
Event &operator+=(EventCallback eventCallback) {
return this->addEventCallback(eventCallback);
}
template<class ClassType>
Event &unsubscribe(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->operator-=(bound);
}
Event &operator-=(EventCallback eventCallback) {
return this->removeEventCallback(eventCallback);
}
template<class ClassType>
bool isListenerRegistered(ClassType *instance, MemberPtr<ClassType> eventCallback) {
const auto bound = [=](EventParameterTypes &&... params) { return ((instance)->*eventCallback)(std::forward<EventParameterTypes>(params)...); };
return this->isListenerRegistered(bound);
}
bool isListenerRegistered(EventCallback eventCallback) {
return findListener(eventCallback) != this->m_eventCallbacks.cend();
}
void operator()(EventParameterTypes ... types) {
this->notify(std::forward<EventParameterTypes>(types)...);
}
void notify(EventParameterTypes ... types) {
for (const auto &it : this->m_eventCallbacks) {
if (it) (it)(std::forward<EventParameterTypes>(types)...);
}
}
private:
std::vector<EventCallback> m_eventCallbacks;
std::mutex m_eventListenerMutex;
typename std::vector<EventCallback>::const_iterator findListener(EventCallback eventCallback) {
for (auto iter = this->m_eventCallbacks.cbegin(); iter != this->m_eventCallbacks.cend(); iter++) {
if (getFunctionAddress(*iter) == getFunctionAddress(eventCallback)) {
return iter;
}
}
return this->m_eventCallbacks.cend();
}
Event &addEventCallback(EventCallback eventCallback) {
auto foundPosition = this->findListener(eventCallback);
if (foundPosition != this->m_eventCallbacks.cend()) {
return *this;
}
this->m_eventCallbacks.emplace_back(eventCallback);
return *this;
}
Event &removeEventCallback(EventCallback eventCallback) {
auto foundPosition = this->findListener(eventCallback);
if (foundPosition == this->m_eventCallbacks.cend()) {
return *this;
}
this->m_eventCallbacks.erase(foundPosition);
return *this;
}
};
#endif //EVENT_HPP
I am working on a messaging system on C++. I have;
class MessageData
{
public:
typedef std::vector<std::shared_ptr<MessageData>> MessageList;
virtual int getValue(std::shared_ptr<int>) { throw "Not implemented!"; };
virtual float getValue(std::shared_ptr<float>) { throw "Not implemented!"; };
virtual std::string getValue(std::shared_ptr<std::string>) { throw "Not implemented!"; };
...
...
virtual ~MessageData() {};
};
template <typename T>
class Message : public MessageData
{
T val;
public:
static std::shared_ptr<Message<T>> Make(T val) { return std::make_shared<Message<T>>(val); };
static T Get(std::shared_ptr<MessageData> in) { return in->getValue(std::make_shared<T>()); };
Message(T i) { val = i; };
T getValue(std::shared_ptr<T> out) override { return *out = val; }
~Message() {};
};
Using these, I can send/receive generic messages of different length conveniently using e.g;
sendMessage(MessageData::MessageList{
Message<std::string>::Make("paint"),
Message<int>::Make(14),
Message<float>::Make(129.3f),
...
});
Then I get the values;
sendMessage(MessageData::MessageList data) {
auto a = Message<std::string>::Get(data[0]);
auto b = Message<int>::Get(data[1]);
auto c = Message<float>::Get(data[2]);
...
}
The downside is that I have to list all the types I need to use in MessageData class. This isn't a big deal as I can limit the types I want to support but I'm really curious about how to templatize the type list without using a 3rd party library. Or is there a completely different and better method that I can use with similar clean syntax and type safety to pass messages around?
One way to make your code more generic is:
template <typename ... Ts>
class MessageDataImp;
template <typename T>
class MessageDataImp<T>
{
public:
virtual ~MessageDataImp() = default;
virtual T getValue(std::shared_ptr<T>) { throw "Not implemented!"; };
};
template <typename T, typename ... Ts>
class MessageDataImp<T, Ts...> : public MessageDataImp<T>, public MessageDataImp<Ts...>
{
public:
using MessageDataImp<T>::getValue;
using MessageDataImp<Ts...>::getValue;
};
template <typename ... Ts>
class MessageDataTs : public MessageDataImp<Ts...>
{
public:
typedef std::vector<std::shared_ptr<MessageDataTs<Ts...>>> MessageList;
};
using MessageData = MessageDataTs<int, float, std::string>;
I think I've developed a decent solution to my problem.
class MessageData {
public:
typedef std::vector<std::shared_ptr<MessageData>> MessageList;
virtual ~MessageData() {};
};
template<typename T>
class Message : public MessageData {
T val;
public:
template<typename U>
friend U GetMessage(std::shared_ptr<MessageData> in);
Message(T i) { val = i; };
};
template<typename T>
T GetMessage(std::shared_ptr<MessageData> in) {
std::shared_ptr<Message<T>> tmp = std::dynamic_pointer_cast<Message<T>>(in);
if (tmp) {
return tmp->val;
}
throw "Incorrect type!";
};
template<typename T>
std::shared_ptr<Message<T>> MakeMessage(T val)
{
return std::make_shared<Message<T>>(val);
};
Then send & receive values using;
sendMessage(MessageData::MessageList{
MakeMessage(std::string("paint")),
MakeMessage(14),
MakeMessage(129.3f),
...
});
sendMessage(MessageData::MessageList data) {
auto a = GetMessage<std::string>(data[0]);
auto b = GetMessage<int>(data[1]);
auto c = GetMessage<float>(data[2]);
...
}
Assuming that it's a simple multiple-reader, multiple-writer message bus based on a non-prioritised queue, I think I'd start with something like this:-
Note that I have used boost::variant/optional. These could easily be replaced with std:: versions if you have those available.
I have used variant because it efficiently caters for most use cases with compile-time safety.
The std/boost::any version would require significant (and possibly unwelcome) care for users of your bus.
#include <iostream>
#include <string>
#include <queue>
#include <thread>
#include <condition_variable>
#include <boost/variant.hpp>
#include <boost/optional.hpp>
template<class Mutex> auto get_lock(Mutex& m) { return std::unique_lock<Mutex>(m); }
template<class...Types>
struct message_bus
{
using message_type = boost::variant<Types...>;
void push(message_type msg) {
auto lock = get_lock(mutex_);
messages_.push(std::move(msg));
lock.unlock();
activity_.notify_one();
}
boost::optional<message_type> wait_pop()
{
boost::optional<message_type> result;
auto lock = get_lock(mutex_);
activity_.wait(lock, [this] { return this->stopped_ or not this->messages_.empty(); });
if (not messages_.empty())
{
result = std::move(messages_.front());
messages_.pop();
}
return result;
}
void signal_stop()
{
auto lock = get_lock(mutex_);
stopped_ = true;
lock.unlock();
activity_.notify_all();
}
std::queue<message_type> messages_;
std::mutex mutex_;
std::condition_variable activity_;
bool stopped_ = false;
};
static std::mutex emit_mutex;
template<class T>
void emit(const T& t)
{
auto lock = get_lock(emit_mutex);
std::cout << std::this_thread::get_id() << ": " << t << std::endl;;
}
int main()
{
using bus_type = message_bus<std::string, int>;
bus_type mb;
std::vector<std::thread> threads;
for (int i = 0 ; i < 10 ; ++i)
{
threads.emplace_back([&]
{
for(;;)
{
auto message = mb.wait_pop();
if (not message)
break;
boost::apply_visitor([](auto&& data) { emit(data); }, message.value());
}
});
}
for (int i = 0 ; i < 1000 ; ++i)
{
mb.push("string: " + std::to_string(i));
mb.push(i);
}
mb.signal_stop();
for (auto& t : threads) if (t.joinable()) t.join();
}
I have an Event class that stores a set of tuples of weak_ptr (the observer) to a function that gets executed when the event is "fired".
The function type is: void(int &) in the example. That is to say, I want to fire the event passing a reference to a value, to have the observer change that value and to verify that the value was changed back in the observed object. The event implementation is variadic by the way, which may complicate the issue (at least the code).
At the moment this is failing. Somewhere along the line the reference is being converted to a non-reference or copied, but I cannot see where.
Full repo is below. Note assert (value != 10) fails, even though I set the value to 1 in the event handler.
#include <memory>
#include <tuple>
#include <vector>
#include <cassert>
#include <functional>
template<class FunctionPrototype>
class Event
{
public:
template<typename... Args>
void operator()(Args... args)
{
for (auto const & listener : Listeners)
{
if (auto locked = std::get<0>(listener).lock())
std::get<1>(listener)(args...);
}
}
template<typename P, typename Q, typename R, typename... Args>
void Attach(P(Q::*f)(Args...), std::shared_ptr<R> const & p)
{
auto w = std::weak_ptr<R>(p);
auto l = [w, f](Args... args) {
if (auto locked = w.lock())
return (*locked.get().*f)(args...);
else
return P();
};
Listeners.push_back(std::make_tuple(std::weak_ptr<void>(w), l));
}
typedef std::tuple<std::weak_ptr<void>, std::function<FunctionPrototype>> event_tuple;
std::vector<event_tuple> Listeners;
};
class Observed : public std::enable_shared_from_this < Observed >
{
public:
int value;
void Fire()
{
value = 10;
TheEvent(value);
assert(value != 10);
}
Event<void(int &)> TheEvent;
};
class Observer : public std::enable_shared_from_this<Observer>
{
public:
void Attach(std::shared_ptr<Observed> const & observed)
{
observed->TheEvent.Attach(&Observer::OnEvent, shared_from_this());
}
void OnEvent(int & value)
{
assert(value == 10);
value = 1;
}
};
int main(void)
{
auto observed = std::make_shared<Observed>();
auto observer1 = std::make_shared<Observer>();
observer1->Attach(observed);
observed->Fire();
return 0;
}
Event::operator() takes arguments by value. Rewrite it as follows:
template<typename... Args>
void operator()(Args&&... args)
{
for (auto const & listener : Listeners)
{
if (auto locked = std::get<0>(listener).lock())
std::get<1>(listener)(std::forward<Args>(args)...);
}
}
assuimng this example code
#include <vector>
#include <iostream>
#include <algorithm>
#include <unistd.h>
template< typename T, typename S >
class MyClass
{
public:
MyClass () : v(10) {}
bool ok () { return true; }
T run (S s, int i) { return v.at(i) = s(); }
private:
std::vector<T> v;
};
int main ()
{
auto f_int = [=]() -> int { sleep(1); return 15; };
MyClass<int, stdmc::function< int(void) > > mc_int;
std::cout << mc_int.run(f_int, 1) << std::endl;
return 0;
}
I want to make partial template specialization for T=void type without rewriting duplicite code. Obviously I only need to reimplement run and possibly get rid of vector as it can't store void type.
There is a lot of literature covering this topic (using enums or int template params), however not a single example of similar problems (i.e. reimplementing only one method and problematic vector).
My motivation is obvious - I need common methods written only once for future code revisions.
My attempt (using code above):
template< typename S >
class MyClass<void, S>
{
public:
// same constructor
// same ok() method
void run (S s, int i) { return s(); }
private:
// without std::vector<T> v;
};
// ----- main ------
auto f_void = [=]() -> void { sleep(1); };
MyClass<void, std::function< void(void) > > mc_void;
std::cout << mc_void.run(f_void, 1) << std::endl;
Is there any way implementing this behaviour in c++11? If so, what is the proper syntax? If not, what do you recommend - copying the whole class while correcting only one method or restructing whole class completely in order to avoid these problems (maybe use vector<T*> which should be able to store void*)?
thanks to Oktalist - this seems to work
#include <vector>
#include <iostream>
#include <algorithm>
#include <unistd.h>
class Base
{
public:
bool ok () { return true; }
};
template< typename T, typename S >
class MyClass : public Base
{
public:
MyClass () : v(10) {}
T run (S s, int i) { return v.at(i) = s(); }
private:
std::vector<T> v;
};
template< typename S >
class MyClass<void, S> : Base
{
public:
MyClass () = default;
void run (S s, int i) { return s(); }
};
int main ()
{
auto f_int = [=]() -> int { sleep(1); return 15; };
MyClass<int, std::function< int(void) > > mc_int;
std::cout << mc_int.run(f_int, 1) << std::endl;
auto f_void = [=]() -> void { sleep(1); std::cout << "done" << std::endl; };
MyClass<void, std::function< void(void) > > mc_void;
mc_void.run(f_void, 1);
return 0;
}