C++ – Implementing events and delegates, signals and slots mechanisms using the standard library

Use the C++ standard library to simply implement event triggering mechanism Event.h

/*
Author:StubbornHuang
Data:2023.1.31
Email:[email protected]
*/


#ifndef _EVENT_H_
#define _EVENT_H_

#include <functional>
#include <map>
#include <type_traits>

#ifndef EVENT_NO_THREAD_SAFETY
#define EVENT_THREAD_SAFETY
#endif // !EVENT_NO_THREAD_SAFETY

#ifdef EVENT_THREAD_SAFETY
#include <atomic>
#include <mutex>
#endif // EVENT_THREAD_SAFETY

#ifdef EVENT_THREAD_SAFETY
#define DELEGATE_ID_TYPE std::atomic_uint64_t
#else
#define DELEGATE_ID_TYPE std::uint64_t
#endif // EVENT_THREAD_SAFETY

namespace stubbornhuang
{
static DELEGATE_ID_TYPE DELEGATE_ID = 1;

template<typename Prototype> class Event;

template<typename ReturnType, typename ...Args>
class Event <ReturnType(Args...)>
{
private:
using return_type = ReturnType;
using function_type = ReturnType(Args ...);
using std_function_type = std::function<function_type>;
using function_pointer = ReturnType(*)(Args...);

private:
class Delegate
{
public:
Delegate() = delete;
Delegate(int id,std_function_type std_function_func)
:m_Handler(nullptr),m_Id(-1)
{
if (std_function_func == nullptr)
return;

m_Id = id;
m_Handler = std_function_func;
}

void Invoke(Args ...args)
{
if (m_Handler != nullptr)
{
m_Handler(args...);
}
}

private:
int m_Id;
std_function_type m_Handler;
};

public:
int AddDelegate(std_function_type std_function_func)
{
if (std_function_func == nullptr)
return -1;
\t\t\t
std::shared_ptr<Delegate> pDelegate = std::make_shared<Delegate>(DELEGATE_ID, std_function_func);

#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY


m_delegates.insert(std::pair<int, std::shared_ptr<Delegate>>(DELEGATE_ID, pDelegate));

return DELEGATE_ID + + ;
}

bool RemoveDelegate(int delegate_id)
{
#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY

if (m_delegates.count(delegate_id) == 0)
return false;

m_delegates.erase(delegate_id);

return true;
}


int operator + = (std_function_type std_function_func)
{
return AddDelegate(std_function_func);
}

bool operator -= (int delegate_id)
{
return RemoveDelegate(delegate_id);
}

void Invoke(Args ...args)
{
#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY

for (const auto & amp; key : m_delegates)
{
key.second->Invoke(args...);
}
}

bool Invoke(int delegate_id, Args ...args)
{
#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY

if (m_delegates.count(delegate_id) == 0)
return false;

m_delegates[delegate_id]->Invoke(args...);


return true;
}

void operator() (Args ...args)
{
#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY

for (const auto & amp; key : m_delegates)
{
key.second->Invoke(args...);
}
}

bool operator() (int delegate_id, Args ...args)
{
#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY

if (m_delegates.count(delegate_id) == 0)
return false;

m_delegates[delegate_id]->Invoke(args...);


return true;
}

int GetDelegateSize()
{
#ifdef EVENT_THREAD_SAFETY
std::lock_guard<std::mutex> guard_mutex(m_event_mutex);
#endif // EVENT_THREAD_SAFETY

return m_delegates.size();
}


private:
std::map<int, std::shared_ptr<Delegate>> m_delegates;

#ifdef EVENT_THREAD_SAFETY

std::mutex m_event_mutex;
#endif // EVENT_THREAD_SAFETY
};
}


#endif // !_EVENT_H_

In the above code, we use template to template the event class Event, and use the variable parameter template typename.. .ArgsThe delegate function parameter list for custom event binding, which can accept multiple different types of parameters. Use std::vector to store the delegate function of std::function for bound events, and overload + = operator adds a delegate function.

Examples of usage of the above event tool class Event are as follows:

without parameters

#include <iostream>

#include "Event.h"

classButton
{
public:
Button()
{

}

virtual~Button()
{

}

public:
stubbornhuang::Event<void()> OnClick;
};

void Click()
{
std::cout << "Button Click" << std::endl;
}


class Example
{
public:
void Click()
{
std::cout << "Example Click" << std::endl;
}
};

int main()
{
Button button;

button.OnClick + = Click; // Static function as delegate function

Example example;
button.OnClick + = std::bind( & amp;Example::Click, example); // Member function acts as delegate function

button.OnClick + = []() { std::cout << "Lambda Click" << std::endl; }; // Anonymous function as delegate function

button.OnClick();

return 0;
}

With parameters:

#include <iostream>

#include "event.h"

void Sum(int a, int b)
{
std::cout << "Sum = " << a + b << std::endl;
}

class Example
{
public:
Example()
{

}
virtual~Example()
{

}

void Print(int a, int b)
{
std::cout << "a = " << a <<" " << "b = " << b << std::endl;
}
};


int main()
{
stubbornhuang::Event<void(int, int)> event;

std::cout << "-----Add Delegate-----" << std::endl;
int sum_fuc_delegate_id = event.AddDelegate(Sum); // static function

event + = [](int a, int b) { std::cout << "Sub = " << a - b << std::endl; }; // lambda function

Example example;
event + = std::bind( & amp;Example::Print, example, std::placeholders::_1, std::placeholders::_2); // class member function

event(1,5);


std::cout<<std::endl << "-----Remove Delegate-----" << std::endl;
event -= (sum_fuc_delegate_id);

event(1, 5);

return 0;
}

This class is thread-safe by default. If you do not want thread-safety, you can define EVENT_NO_THREAD_SAFETY before including the header file. This class is also a template class and supports the definition of events with any return type and variable parameters. class, and delete the delegate function by returning the delegate function ID.

syntaxbug.com © 2021 All Rights Reserved.