Simple implementation of signals and slots mechanism using templates

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP





.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty margin-bottom:0;







up vote
6
down vote

favorite












I tried to implement a simple signal&slots mechanism for some DSP software that runs on embedded Linux. I would like if somebody can review this code and give me some guidelines. Any review and advice is welcome.
Main idea was to have a signal as a data member inside any of my classes that receive data from DSP and emit that data to multiple of dsp pipeline processing classes.



Classes and their description




  • slot_key - This is class responsible for assigning ID to any newly created instance of class slot. Implementation of this class is something I really don't like. This class is used as a base class of class slot. slot_key::slot_id is used in class signal as a key to store said slot and to be able to disconnect slot from signal.
    (although in my case connected slots do not disconnect during life time of the application).

Code:



using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;



  • slot - This is class that holds any callable object client programmer wants to connect with wanted signal. slot is able to hold any callable object (functor, lambda, class instance + function member of that class or global function), only limitation for underlying object is to be callable with argumets signal emits.

Code:



template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) :
_method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;



  • signal - This is class used for emitting data to slots. It's implementation is simple. It provides interface for connecting any callable element and returning slot_id that is used if disconnect from signal is wanted.

Code:



template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;

// Copied signal doesnt have connected slots from original
signal(const signal&) :
slots(std::map<slot_id_t, slot<Args...>>())

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
// TODO: in C++17 is_invocable can be used
static_assert(is_callable<T>::value, "Parameter not invokable.
Pass method you want to use as slot");
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;


File with all classes and usage example (it should be able to compile as is)



main.cpp:



#include <iostream>
#include <vector>
#include <map>
#include <functional>
#include <memory>
#include <type_traits>


template<typename C, typename = void>
struct is_callable : std::false_type ;

template<typename C>
struct is_callable<C, std::void_t<decltype(&C::operator())>> : std::true_type ;


template<class ...Args>
struct signal;

using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;

template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) : _method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;

template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;
signal(const signal&) : slots(std::map<slot_id_t, slot<Args...>>()) // Copied signal doesnt have connected slots from original

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
static_assert(is_callable<T>::value, "Parameter not invokable. Pass method you want to use as slot"); // TODO: in C++17 is_invocable can be used
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;

struct signal_provider
signal<int, int> sig;
;

struct slot_1
void on_signal(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

struct slot_2
void on_signal2(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

auto lambda = (int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;
;

void global_method(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;


int main()
slot_1 slot1;
slot_2 slot2;
signal_provider signal;
auto lambda_w_capture_list = [&](int a, int b) std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl; ;

auto sl_id = signal.sig.connect(slot2, &slot_2::on_signal2);
signal.sig.connect(slot1, &slot_1::on_signal);
signal.sig.connect(lambda);
signal.sig.connect(global_method);
signal.sig.connect(lambda_w_capture_list);
signal.sig.emit(5, 6);

signal.sig.disconnect(sl_id);
signal.sig.emit(8, 8);

return 0;







share|improve this question

















  • 1




    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    – Heslacher
    Feb 27 at 14:15
















up vote
6
down vote

favorite












I tried to implement a simple signal&slots mechanism for some DSP software that runs on embedded Linux. I would like if somebody can review this code and give me some guidelines. Any review and advice is welcome.
Main idea was to have a signal as a data member inside any of my classes that receive data from DSP and emit that data to multiple of dsp pipeline processing classes.



Classes and their description




  • slot_key - This is class responsible for assigning ID to any newly created instance of class slot. Implementation of this class is something I really don't like. This class is used as a base class of class slot. slot_key::slot_id is used in class signal as a key to store said slot and to be able to disconnect slot from signal.
    (although in my case connected slots do not disconnect during life time of the application).

Code:



using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;



  • slot - This is class that holds any callable object client programmer wants to connect with wanted signal. slot is able to hold any callable object (functor, lambda, class instance + function member of that class or global function), only limitation for underlying object is to be callable with argumets signal emits.

Code:



template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) :
_method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;



  • signal - This is class used for emitting data to slots. It's implementation is simple. It provides interface for connecting any callable element and returning slot_id that is used if disconnect from signal is wanted.

Code:



template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;

// Copied signal doesnt have connected slots from original
signal(const signal&) :
slots(std::map<slot_id_t, slot<Args...>>())

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
// TODO: in C++17 is_invocable can be used
static_assert(is_callable<T>::value, "Parameter not invokable.
Pass method you want to use as slot");
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;


File with all classes and usage example (it should be able to compile as is)



main.cpp:



#include <iostream>
#include <vector>
#include <map>
#include <functional>
#include <memory>
#include <type_traits>


template<typename C, typename = void>
struct is_callable : std::false_type ;

template<typename C>
struct is_callable<C, std::void_t<decltype(&C::operator())>> : std::true_type ;


template<class ...Args>
struct signal;

using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;

template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) : _method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;

template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;
signal(const signal&) : slots(std::map<slot_id_t, slot<Args...>>()) // Copied signal doesnt have connected slots from original

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
static_assert(is_callable<T>::value, "Parameter not invokable. Pass method you want to use as slot"); // TODO: in C++17 is_invocable can be used
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;

struct signal_provider
signal<int, int> sig;
;

struct slot_1
void on_signal(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

struct slot_2
void on_signal2(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

auto lambda = (int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;
;

void global_method(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;


int main()
slot_1 slot1;
slot_2 slot2;
signal_provider signal;
auto lambda_w_capture_list = [&](int a, int b) std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl; ;

auto sl_id = signal.sig.connect(slot2, &slot_2::on_signal2);
signal.sig.connect(slot1, &slot_1::on_signal);
signal.sig.connect(lambda);
signal.sig.connect(global_method);
signal.sig.connect(lambda_w_capture_list);
signal.sig.emit(5, 6);

signal.sig.disconnect(sl_id);
signal.sig.emit(8, 8);

return 0;







share|improve this question

















  • 1




    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    – Heslacher
    Feb 27 at 14:15












up vote
6
down vote

favorite









up vote
6
down vote

favorite











I tried to implement a simple signal&slots mechanism for some DSP software that runs on embedded Linux. I would like if somebody can review this code and give me some guidelines. Any review and advice is welcome.
Main idea was to have a signal as a data member inside any of my classes that receive data from DSP and emit that data to multiple of dsp pipeline processing classes.



Classes and their description




  • slot_key - This is class responsible for assigning ID to any newly created instance of class slot. Implementation of this class is something I really don't like. This class is used as a base class of class slot. slot_key::slot_id is used in class signal as a key to store said slot and to be able to disconnect slot from signal.
    (although in my case connected slots do not disconnect during life time of the application).

Code:



using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;



  • slot - This is class that holds any callable object client programmer wants to connect with wanted signal. slot is able to hold any callable object (functor, lambda, class instance + function member of that class or global function), only limitation for underlying object is to be callable with argumets signal emits.

Code:



template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) :
_method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;



  • signal - This is class used for emitting data to slots. It's implementation is simple. It provides interface for connecting any callable element and returning slot_id that is used if disconnect from signal is wanted.

Code:



template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;

// Copied signal doesnt have connected slots from original
signal(const signal&) :
slots(std::map<slot_id_t, slot<Args...>>())

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
// TODO: in C++17 is_invocable can be used
static_assert(is_callable<T>::value, "Parameter not invokable.
Pass method you want to use as slot");
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;


File with all classes and usage example (it should be able to compile as is)



main.cpp:



#include <iostream>
#include <vector>
#include <map>
#include <functional>
#include <memory>
#include <type_traits>


template<typename C, typename = void>
struct is_callable : std::false_type ;

template<typename C>
struct is_callable<C, std::void_t<decltype(&C::operator())>> : std::true_type ;


template<class ...Args>
struct signal;

using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;

template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) : _method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;

template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;
signal(const signal&) : slots(std::map<slot_id_t, slot<Args...>>()) // Copied signal doesnt have connected slots from original

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
static_assert(is_callable<T>::value, "Parameter not invokable. Pass method you want to use as slot"); // TODO: in C++17 is_invocable can be used
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;

struct signal_provider
signal<int, int> sig;
;

struct slot_1
void on_signal(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

struct slot_2
void on_signal2(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

auto lambda = (int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;
;

void global_method(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;


int main()
slot_1 slot1;
slot_2 slot2;
signal_provider signal;
auto lambda_w_capture_list = [&](int a, int b) std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl; ;

auto sl_id = signal.sig.connect(slot2, &slot_2::on_signal2);
signal.sig.connect(slot1, &slot_1::on_signal);
signal.sig.connect(lambda);
signal.sig.connect(global_method);
signal.sig.connect(lambda_w_capture_list);
signal.sig.emit(5, 6);

signal.sig.disconnect(sl_id);
signal.sig.emit(8, 8);

return 0;







share|improve this question













I tried to implement a simple signal&slots mechanism for some DSP software that runs on embedded Linux. I would like if somebody can review this code and give me some guidelines. Any review and advice is welcome.
Main idea was to have a signal as a data member inside any of my classes that receive data from DSP and emit that data to multiple of dsp pipeline processing classes.



Classes and their description




  • slot_key - This is class responsible for assigning ID to any newly created instance of class slot. Implementation of this class is something I really don't like. This class is used as a base class of class slot. slot_key::slot_id is used in class signal as a key to store said slot and to be able to disconnect slot from signal.
    (although in my case connected slots do not disconnect during life time of the application).

Code:



using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;



  • slot - This is class that holds any callable object client programmer wants to connect with wanted signal. slot is able to hold any callable object (functor, lambda, class instance + function member of that class or global function), only limitation for underlying object is to be callable with argumets signal emits.

Code:



template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) :
_method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;



  • signal - This is class used for emitting data to slots. It's implementation is simple. It provides interface for connecting any callable element and returning slot_id that is used if disconnect from signal is wanted.

Code:



template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;

// Copied signal doesnt have connected slots from original
signal(const signal&) :
slots(std::map<slot_id_t, slot<Args...>>())

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
// TODO: in C++17 is_invocable can be used
static_assert(is_callable<T>::value, "Parameter not invokable.
Pass method you want to use as slot");
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;


File with all classes and usage example (it should be able to compile as is)



main.cpp:



#include <iostream>
#include <vector>
#include <map>
#include <functional>
#include <memory>
#include <type_traits>


template<typename C, typename = void>
struct is_callable : std::false_type ;

template<typename C>
struct is_callable<C, std::void_t<decltype(&C::operator())>> : std::true_type ;


template<class ...Args>
struct signal;

using slot_id_t = uint64_t;
struct slot_key
slot_id_t slot_id;
slot_key() : slot_id(0)
slot_key(const slot_key&) : slot_id(0)

protected:
static uint64_t _slots_id;
slot_key(uint64_t) : slot_id(++_slots_id)
;

uint64_t slot_key::_slots_id = 0;

template <class... Args>
struct slot : slot_key
slot()

template<class T, class R>
slot(T& t, R r) :
slot_key(0),
_pcallback (new class_holder<T>(t, r))


template<class T>
slot(T& t) :
slot_key(0),
_pcallback (new functor_holder<T>(t))


slot(void(fp)(Args...)) :
slot_key(0),
_pcallback (new function_holder<void(Args...)>(fp))


void operator()(Args... args)
(*_pcallback)(args...);


private:
template <class... A>
struct call_interface
virtual void operator ()(A... args) = 0;
;

template <class owner>
struct class_holder : call_interface<Args...>
using method_type = void(owner::*)(Args...);
class_holder(owner &o, method_type method) : _method(method), _owner(o)


void operator ()(Args... args)
(_owner.*_method)(args...);


method_type _method;
owner& _owner;
;

template <class owner>
struct functor_holder : call_interface<Args...>
functor_holder(owner &o) : _owner(o)


void operator ()(Args... args)
_owner(args...);


owner& _owner;
;

template <class fn>
struct function_holder : call_interface<Args...>
function_holder(fn* func) : _func(func)


void operator ()(Args... args)
(*_func)(args...);


fn* _func;
;

std::unique_ptr<call_interface<Args...>> _pcallback;
;

template <class... Args>
struct signal

signal() = default;
signal(signal&&) = default;
signal(const signal&) : slots(std::map<slot_id_t, slot<Args...>>()) // Copied signal doesnt have connected slots from original

void emit(Args... args)
for (auto &s : slots)
s.second(args...);


template<class T> // for classes and class function member
slot_id_t connect(T& t, void(T::*fp)(Args...))
return add_slot(slot<Args...>(t, fp));


template<class T> // for functors
slot_id_t connect(T& t)
static_assert(is_callable<T>::value, "Parameter not invokable. Pass method you want to use as slot"); // TODO: in C++17 is_invocable can be used
return add_slot(slot<Args...>(t));


slot_id_t connect(void(fp)(Args...)) // for global functions
return add_slot(slot<Args...>(fp));


void disconnect(slot_id_t slot)
slots.erase(slot);


private:
slot_id_t add_slot(slot<Args...>&& t)
slots[t.slot_id] = std::move(t);
return t.slot_id;


std::map<slot_id_t, slot<Args...>> slots;
;

struct signal_provider
signal<int, int> sig;
;

struct slot_1
void on_signal(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

struct slot_2
void on_signal2(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;

;

auto lambda = (int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;
;

void global_method(int a, int b)
std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl;


int main()
slot_1 slot1;
slot_2 slot2;
signal_provider signal;
auto lambda_w_capture_list = [&](int a, int b) std::cout << __PRETTY_FUNCTION__ << " " << a << ", " << b << std::endl; ;

auto sl_id = signal.sig.connect(slot2, &slot_2::on_signal2);
signal.sig.connect(slot1, &slot_1::on_signal);
signal.sig.connect(lambda);
signal.sig.connect(global_method);
signal.sig.connect(lambda_w_capture_list);
signal.sig.emit(5, 6);

signal.sig.disconnect(sl_id);
signal.sig.emit(8, 8);

return 0;









share|improve this question












share|improve this question




share|improve this question








edited Feb 27 at 14:15









Heslacher

43.9k359152




43.9k359152









asked Feb 26 at 11:56









Nenad

334




334







  • 1




    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    – Heslacher
    Feb 27 at 14:15












  • 1




    Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
    – Heslacher
    Feb 27 at 14:15







1




1




Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
– Heslacher
Feb 27 at 14:15




Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers.
– Heslacher
Feb 27 at 14:15










1 Answer
1






active

oldest

votes

















up vote
6
down vote



accepted










[code] uint64_t



It should be std::uint64_t, not uint64_t for key indexes (the latter is the C version). Or better: use std::size_t, which is the standard indexing type.




[code] passing arguments



At the moment, all the parameter pack arguments to the function calls are being passed by value. This should instead use perfect forwarding to prevent unnecessary copies (and allow passing non-copyable objects by reference or move):



void operator()(Args&&... args) 
(*_pcallback)(std::forward<Args>(args)...);


...

void emit(Args&&... args)
for (auto &s : slots)
s.second(std::forward<Args>(args)...);




[code / design] signal copy / move



There doesn't seem to be any point in allowing copying a signal if it doesn't have the connected slots from the original, and this is very surprising behaviour for the user. Either disallow copying, or copy the slots completely.



Copy / move assignment operators should be supplied to match the copy / move constructors.




[design] slot_key



The slot_id inside the slot_key class is duplicated in the map structure. Also, the slot class itself doesn't seem to be used or usefully accessible outside of the signal. I'd recommend either:



  1. removing the slot_id member from the slot class, and removing the slot_key base class. Or...

  2. changing the slots data structure to a std::set, and providing the relevant comparison operator for the slot class.

If you do choose to keep the slot_id member, it would probably be better off in the slot class, rather than a base class (prefer composition to inheritance), and the key generation part might be better off as a key_generator object, existing in the signal.




[design] slot



All the functionality of the slot class can be handled by std::function, which makes the class redundant:



#include <iostream>
#include <map>
#include <functional>
#include <cassert>

using slot_id_t = std::uint64_t;

struct slot_key_generator
slot_id_t _slots_id = 0;

slot_id_t get_next()
return _slots_id++;

;

template <class... Args>
struct signal

using function_t = std::function<void(Args...)>;

signal() = default;
signal(signal&&) = default;
signal(const signal&) = default;

signal& operator=(signal&&) = default;
signal& operator=(signal const&) = default;

void emit(Args&&... args)
for (auto &s : _slots)
s.second(std::forward<Args>(args)...);


slot_id_t connect(function_t function)
assert(function); // no empty functions!
return add_slot(std::move(function));


void disconnect(slot_id_t slot)
_slots.erase(slot);


private:

slot_id_t add_slot(function_t&& t)
auto key = _generator.get_next();
_slots.emplace(key, std::move(t));
return key;


slot_key_generator _generator;
std::map<slot_id_t, function_t> _slots;
;


The user code is the same, except for functors, which can use std::bind (or a lambda if it's easier):



using namespace std::placeholders;
auto sl_id = signal.sig.connect(std::bind(&slot_2::on_signal2, &slot2, _1, _2));
signal.sig.connect(std::bind(&slot_1::on_signal, &slot1, _1, _2));





share|improve this answer





















  • This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
    – Nenad
    Feb 26 at 17:40






  • 1




    @Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
    – user673679
    Feb 26 at 17:47










  • It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
    – Nenad
    Feb 26 at 18:02











Your Answer




StackExchange.ifUsing("editor", function ()
return StackExchange.using("mathjaxEditing", function ()
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
);
);
, "mathjax-editing");

StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "196"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
convertImagesToLinks: false,
noModals: false,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);








 

draft saved


draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188368%2fsimple-implementation-of-signals-and-slots-mechanism-using-templates%23new-answer', 'question_page');

);

Post as a guest






























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes








up vote
6
down vote



accepted










[code] uint64_t



It should be std::uint64_t, not uint64_t for key indexes (the latter is the C version). Or better: use std::size_t, which is the standard indexing type.




[code] passing arguments



At the moment, all the parameter pack arguments to the function calls are being passed by value. This should instead use perfect forwarding to prevent unnecessary copies (and allow passing non-copyable objects by reference or move):



void operator()(Args&&... args) 
(*_pcallback)(std::forward<Args>(args)...);


...

void emit(Args&&... args)
for (auto &s : slots)
s.second(std::forward<Args>(args)...);




[code / design] signal copy / move



There doesn't seem to be any point in allowing copying a signal if it doesn't have the connected slots from the original, and this is very surprising behaviour for the user. Either disallow copying, or copy the slots completely.



Copy / move assignment operators should be supplied to match the copy / move constructors.




[design] slot_key



The slot_id inside the slot_key class is duplicated in the map structure. Also, the slot class itself doesn't seem to be used or usefully accessible outside of the signal. I'd recommend either:



  1. removing the slot_id member from the slot class, and removing the slot_key base class. Or...

  2. changing the slots data structure to a std::set, and providing the relevant comparison operator for the slot class.

If you do choose to keep the slot_id member, it would probably be better off in the slot class, rather than a base class (prefer composition to inheritance), and the key generation part might be better off as a key_generator object, existing in the signal.




[design] slot



All the functionality of the slot class can be handled by std::function, which makes the class redundant:



#include <iostream>
#include <map>
#include <functional>
#include <cassert>

using slot_id_t = std::uint64_t;

struct slot_key_generator
slot_id_t _slots_id = 0;

slot_id_t get_next()
return _slots_id++;

;

template <class... Args>
struct signal

using function_t = std::function<void(Args...)>;

signal() = default;
signal(signal&&) = default;
signal(const signal&) = default;

signal& operator=(signal&&) = default;
signal& operator=(signal const&) = default;

void emit(Args&&... args)
for (auto &s : _slots)
s.second(std::forward<Args>(args)...);


slot_id_t connect(function_t function)
assert(function); // no empty functions!
return add_slot(std::move(function));


void disconnect(slot_id_t slot)
_slots.erase(slot);


private:

slot_id_t add_slot(function_t&& t)
auto key = _generator.get_next();
_slots.emplace(key, std::move(t));
return key;


slot_key_generator _generator;
std::map<slot_id_t, function_t> _slots;
;


The user code is the same, except for functors, which can use std::bind (or a lambda if it's easier):



using namespace std::placeholders;
auto sl_id = signal.sig.connect(std::bind(&slot_2::on_signal2, &slot2, _1, _2));
signal.sig.connect(std::bind(&slot_1::on_signal, &slot1, _1, _2));





share|improve this answer





















  • This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
    – Nenad
    Feb 26 at 17:40






  • 1




    @Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
    – user673679
    Feb 26 at 17:47










  • It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
    – Nenad
    Feb 26 at 18:02















up vote
6
down vote



accepted










[code] uint64_t



It should be std::uint64_t, not uint64_t for key indexes (the latter is the C version). Or better: use std::size_t, which is the standard indexing type.




[code] passing arguments



At the moment, all the parameter pack arguments to the function calls are being passed by value. This should instead use perfect forwarding to prevent unnecessary copies (and allow passing non-copyable objects by reference or move):



void operator()(Args&&... args) 
(*_pcallback)(std::forward<Args>(args)...);


...

void emit(Args&&... args)
for (auto &s : slots)
s.second(std::forward<Args>(args)...);




[code / design] signal copy / move



There doesn't seem to be any point in allowing copying a signal if it doesn't have the connected slots from the original, and this is very surprising behaviour for the user. Either disallow copying, or copy the slots completely.



Copy / move assignment operators should be supplied to match the copy / move constructors.




[design] slot_key



The slot_id inside the slot_key class is duplicated in the map structure. Also, the slot class itself doesn't seem to be used or usefully accessible outside of the signal. I'd recommend either:



  1. removing the slot_id member from the slot class, and removing the slot_key base class. Or...

  2. changing the slots data structure to a std::set, and providing the relevant comparison operator for the slot class.

If you do choose to keep the slot_id member, it would probably be better off in the slot class, rather than a base class (prefer composition to inheritance), and the key generation part might be better off as a key_generator object, existing in the signal.




[design] slot



All the functionality of the slot class can be handled by std::function, which makes the class redundant:



#include <iostream>
#include <map>
#include <functional>
#include <cassert>

using slot_id_t = std::uint64_t;

struct slot_key_generator
slot_id_t _slots_id = 0;

slot_id_t get_next()
return _slots_id++;

;

template <class... Args>
struct signal

using function_t = std::function<void(Args...)>;

signal() = default;
signal(signal&&) = default;
signal(const signal&) = default;

signal& operator=(signal&&) = default;
signal& operator=(signal const&) = default;

void emit(Args&&... args)
for (auto &s : _slots)
s.second(std::forward<Args>(args)...);


slot_id_t connect(function_t function)
assert(function); // no empty functions!
return add_slot(std::move(function));


void disconnect(slot_id_t slot)
_slots.erase(slot);


private:

slot_id_t add_slot(function_t&& t)
auto key = _generator.get_next();
_slots.emplace(key, std::move(t));
return key;


slot_key_generator _generator;
std::map<slot_id_t, function_t> _slots;
;


The user code is the same, except for functors, which can use std::bind (or a lambda if it's easier):



using namespace std::placeholders;
auto sl_id = signal.sig.connect(std::bind(&slot_2::on_signal2, &slot2, _1, _2));
signal.sig.connect(std::bind(&slot_1::on_signal, &slot1, _1, _2));





share|improve this answer





















  • This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
    – Nenad
    Feb 26 at 17:40






  • 1




    @Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
    – user673679
    Feb 26 at 17:47










  • It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
    – Nenad
    Feb 26 at 18:02













up vote
6
down vote



accepted







up vote
6
down vote



accepted






[code] uint64_t



It should be std::uint64_t, not uint64_t for key indexes (the latter is the C version). Or better: use std::size_t, which is the standard indexing type.




[code] passing arguments



At the moment, all the parameter pack arguments to the function calls are being passed by value. This should instead use perfect forwarding to prevent unnecessary copies (and allow passing non-copyable objects by reference or move):



void operator()(Args&&... args) 
(*_pcallback)(std::forward<Args>(args)...);


...

void emit(Args&&... args)
for (auto &s : slots)
s.second(std::forward<Args>(args)...);




[code / design] signal copy / move



There doesn't seem to be any point in allowing copying a signal if it doesn't have the connected slots from the original, and this is very surprising behaviour for the user. Either disallow copying, or copy the slots completely.



Copy / move assignment operators should be supplied to match the copy / move constructors.




[design] slot_key



The slot_id inside the slot_key class is duplicated in the map structure. Also, the slot class itself doesn't seem to be used or usefully accessible outside of the signal. I'd recommend either:



  1. removing the slot_id member from the slot class, and removing the slot_key base class. Or...

  2. changing the slots data structure to a std::set, and providing the relevant comparison operator for the slot class.

If you do choose to keep the slot_id member, it would probably be better off in the slot class, rather than a base class (prefer composition to inheritance), and the key generation part might be better off as a key_generator object, existing in the signal.




[design] slot



All the functionality of the slot class can be handled by std::function, which makes the class redundant:



#include <iostream>
#include <map>
#include <functional>
#include <cassert>

using slot_id_t = std::uint64_t;

struct slot_key_generator
slot_id_t _slots_id = 0;

slot_id_t get_next()
return _slots_id++;

;

template <class... Args>
struct signal

using function_t = std::function<void(Args...)>;

signal() = default;
signal(signal&&) = default;
signal(const signal&) = default;

signal& operator=(signal&&) = default;
signal& operator=(signal const&) = default;

void emit(Args&&... args)
for (auto &s : _slots)
s.second(std::forward<Args>(args)...);


slot_id_t connect(function_t function)
assert(function); // no empty functions!
return add_slot(std::move(function));


void disconnect(slot_id_t slot)
_slots.erase(slot);


private:

slot_id_t add_slot(function_t&& t)
auto key = _generator.get_next();
_slots.emplace(key, std::move(t));
return key;


slot_key_generator _generator;
std::map<slot_id_t, function_t> _slots;
;


The user code is the same, except for functors, which can use std::bind (or a lambda if it's easier):



using namespace std::placeholders;
auto sl_id = signal.sig.connect(std::bind(&slot_2::on_signal2, &slot2, _1, _2));
signal.sig.connect(std::bind(&slot_1::on_signal, &slot1, _1, _2));





share|improve this answer













[code] uint64_t



It should be std::uint64_t, not uint64_t for key indexes (the latter is the C version). Or better: use std::size_t, which is the standard indexing type.




[code] passing arguments



At the moment, all the parameter pack arguments to the function calls are being passed by value. This should instead use perfect forwarding to prevent unnecessary copies (and allow passing non-copyable objects by reference or move):



void operator()(Args&&... args) 
(*_pcallback)(std::forward<Args>(args)...);


...

void emit(Args&&... args)
for (auto &s : slots)
s.second(std::forward<Args>(args)...);




[code / design] signal copy / move



There doesn't seem to be any point in allowing copying a signal if it doesn't have the connected slots from the original, and this is very surprising behaviour for the user. Either disallow copying, or copy the slots completely.



Copy / move assignment operators should be supplied to match the copy / move constructors.




[design] slot_key



The slot_id inside the slot_key class is duplicated in the map structure. Also, the slot class itself doesn't seem to be used or usefully accessible outside of the signal. I'd recommend either:



  1. removing the slot_id member from the slot class, and removing the slot_key base class. Or...

  2. changing the slots data structure to a std::set, and providing the relevant comparison operator for the slot class.

If you do choose to keep the slot_id member, it would probably be better off in the slot class, rather than a base class (prefer composition to inheritance), and the key generation part might be better off as a key_generator object, existing in the signal.




[design] slot



All the functionality of the slot class can be handled by std::function, which makes the class redundant:



#include <iostream>
#include <map>
#include <functional>
#include <cassert>

using slot_id_t = std::uint64_t;

struct slot_key_generator
slot_id_t _slots_id = 0;

slot_id_t get_next()
return _slots_id++;

;

template <class... Args>
struct signal

using function_t = std::function<void(Args...)>;

signal() = default;
signal(signal&&) = default;
signal(const signal&) = default;

signal& operator=(signal&&) = default;
signal& operator=(signal const&) = default;

void emit(Args&&... args)
for (auto &s : _slots)
s.second(std::forward<Args>(args)...);


slot_id_t connect(function_t function)
assert(function); // no empty functions!
return add_slot(std::move(function));


void disconnect(slot_id_t slot)
_slots.erase(slot);


private:

slot_id_t add_slot(function_t&& t)
auto key = _generator.get_next();
_slots.emplace(key, std::move(t));
return key;


slot_key_generator _generator;
std::map<slot_id_t, function_t> _slots;
;


The user code is the same, except for functors, which can use std::bind (or a lambda if it's easier):



using namespace std::placeholders;
auto sl_id = signal.sig.connect(std::bind(&slot_2::on_signal2, &slot2, _1, _2));
signal.sig.connect(std::bind(&slot_1::on_signal, &slot1, _1, _2));






share|improve this answer













share|improve this answer



share|improve this answer











answered Feb 26 at 16:31









user673679

1,042518




1,042518











  • This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
    – Nenad
    Feb 26 at 17:40






  • 1




    @Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
    – user673679
    Feb 26 at 17:47










  • It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
    – Nenad
    Feb 26 at 18:02

















  • This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
    – Nenad
    Feb 26 at 17:40






  • 1




    @Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
    – user673679
    Feb 26 at 17:47










  • It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
    – Nenad
    Feb 26 at 18:02
















This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
– Nenad
Feb 26 at 17:40




This is great solution, thank you. There was one problem with using std::bind that was the main reason I didn't go with std::function<void(Args...)>. I don't know how to check for type safety when connecting slot. E.g. there could be a struct slot3 void on_signal(float f) . That struct can be connected to signal but it's arguments don't match.: Compilaton for: signal.sig.connect(std::bind(&slot_3::on_signal3, &slot3, _1)) passes. Do you have an idea how could I force usage of function members with exact signature ?
– Nenad
Feb 26 at 17:40




1




1




@Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
– user673679
Feb 26 at 17:47




@Nenad good point. :) Perhaps add back the class + function pointer version of connect(), and call the std::function version internally: return connect([&, fp] (Args&&... args) (t.*fp)(std::forward<Args>(args)...); );
– user673679
Feb 26 at 17:47












It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
– Nenad
Feb 26 at 18:02





It works perfectly. Thank you. To be honest I'm a bit sad that whole slot chemistry was useless at the end :) but yours solution is way more elegant.
– Nenad
Feb 26 at 18:02













 

draft saved


draft discarded


























 


draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f188368%2fsimple-implementation-of-signals-and-slots-mechanism-using-templates%23new-answer', 'question_page');

);

Post as a guest













































































Popular posts from this blog

Greedy Best First Search implementation in Rust

Function to Return a JSON Like Objects Using VBA Collections and Arrays

C++11 CLH Lock Implementation