昆明做网站的公司哪家好,深圳做棋牌网站建设哪家公司收费合理,医疗电子网站建设,关于单位网站建设的报告Qt中的信号与槽机制很好用#xff0c;然而只在Qt环境中。在现代 C 编程中#xff0c;对象间的通信是一个核心问题。为了解决这个问题#xff0c;许多库提供了信号和槽#xff08;Signals and Slots#xff09;机制。今天推荐分享一个轻量级的实现#xff1a;sigslot 库。… Qt中的信号与槽机制很好用然而只在Qt环境中。在现代 C 编程中对象间的通信是一个核心问题。为了解决这个问题许多库提供了信号和槽Signals and Slots机制。今天推荐分享一个轻量级的实现sigslot 库。源码也很小用来学习c的新特性也是不错的选择。 介绍
sigslot 是一个轻量级的 C 信号和槽库它提供了一种类型安全的机制来处理对象之间的通信。信号和槽机制允许对象在状态变化时通知其他对象而无需直接调用它们的成员函数。这种机制有助于减少对象之间的耦合使代码更易于维护和扩展。
仓库地址
你可以在 GitHub 上找到 sigslot 库的源码 sigslot GitHub 仓库
https://github.com/palacaze/sigslot
优缺点
优点
类型安全sigslot 提供了编译时的类型检查确保信号和槽之间的参数类型匹配。多线程支持sigslot 支持多线程环境可以安全地在不同线程之间传递信号。自动连接管理sigslot 会自动管理信号和槽之间的连接当对象被销毁时相关的连接也会自动断开。灵活性sigslot 允许一个信号连接到多个槽也允许一个槽连接到多个信号。简单易用sigslot 的 API 设计简洁易于理解和使用。
注意该库需要c工具链最低支持c14标准。
缺点
功能相对简单相比于 Boost.Signals2 或 Qt 的信号和槽机制sigslot 的功能较为简单可能不适合需要复杂信号和槽机制的项目。文档和社区支持有限作为一个相对小众的库sigslot 的文档和社区支持可能不如一些主流库那么丰富。
sigslot作用
Sigslot是信号signal和槽slot的结合是一种用于处理C对象通信的机制。信号是一个对象发出的事件或状态的通知而槽则是响应信号并执行特定动作的函数。
Sigslot 的作用一句话表式就是为了解耦。例如有两个类 A 和 B如果 B 使用 A, 就必须在 B 类中写入与 A 类有关的代码。
使用Sigslot的主要原因包括
解耦对象之间的通信Sigslot可以帮助对象完全独立通信减少对象之间的耦合度提高程序的可维护性和可扩展性。简化对象之间的交互Sigslot可以让对象之间的交互变得更加灵活和简单使得代码更易于阅读和维护。支持事件驱动编程Sigslot可以方便地实现事件驱动的编程模式使得代码结构清晰易于理解。
总的来说Sigslot可以帮助简化C对象之间的通信和交互使得代码更加清晰和可维护。
实现原理
sigslot的原理其实非常简单它就是一个变化的观察者模式。观察者模式如下所示 观察者模式首先让 Observer(“观察者”)对象 注册到 Subject(“被观察者”) 对象中。当 Subject 状态发生变化时遍历所有注册到自己的 Observer 对象并调用它们的 notify方法。
sigslot与观察者模式类似它使用signal(“信号”)和slot(槽)区别在于 signal 主动连接自己感兴趣的类及其方法将它们保存到自己的列表中。当发射信号时它遍历所有的连接调用 slot“槽” 方法。
简单使用
#include sigslot/signal.hpp
#include iostreamvoid f() { std::cout free function\n; }struct s {void m() { std::cout member function\n; }static void sm() { std::cout static member function\n; }
};struct o {void operator()() { std::cout function object\n; }
};int main() {s d;auto lambda []() { std::cout lambda\n; };// declare a signal instance with no argumentssigslot::signal sig;// sigslot::signal will connect to any callable provided it has compatible// arguments. Here are diverse examplessig.connect(f);sig.connect(s::m, d);sig.connect(s::sm);sig.connect(o());sig.connect(lambda);// Avoid hitting bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id68071// on old GCC compilers
#ifndef __clang__
#if GCC_VERSION 70300auto gen_lambda [](auto ... /*a*/) { std::cout generic lambda\n; };sig.connect(gen_lambda);
#endif
#endif// emit a signalsig();return 0;
}带参数的使用
#include sigslot/signal.hpp
#include iostream
#include stringstruct foo {// Notice how we accept a double as first argument here// This is fine because float is convertible to doublevoid bar(float d, int i, bool b, std::string s) {s b ? std::to_string(i) : std::to_string(d);}
};// Function objects can cope with default arguments and overloading.
// It does not work with static and member functions.
struct obj {void operator()(float, int, bool, std::string , int 0) {std::cout I was here\n;}void operator()() {}
};// A generic function object that deals with any input argument
struct printer {template typename T, typename... Tsvoid operator()(T a, Ts ...args) const {std::cout a;(void)std::initializer_listint{((void)(std::cout std::forwardTs(args)), 1)...};std::cout \n;}
};int main() {// declare a signal with float, int, bool and string argumentssigslot::signalfloat, int, bool, std::string sig;// a Generic lambda that prints its arguments to stdoutauto lambda_printer [] (auto a, auto ...args) {std::cout a;(void)std::initializer_listint{((void)(std::cout args), 1)...};std::cout \n;};// connect the slotsfoo ff;sig.connect(printer());sig.connect(foo::bar, ff);sig.connect(lambda_printer);sig.connect(obj());float f 1.f;short i 2;std::string s 0;// emit a signalsig(f, i, false, s);sig(f, i, true, s);return 0;
}
使用示例
以下是一个简单的 sigslot 示例展示了如何使用信号和槽机制
#include iostream
#include sigslot/signal.hppclass Button {
public:sigslot::signal clicked;
};class Dialog {
public:void handleButtonClick() {std::cout Button clicked! std::endl;}
};int main() {Button button;Dialog dialog;// Connect the buttons clicked signal to the dialogs handleButtonClick slotbutton.clicked.connect(dialog, Dialog::handleButtonClick);// Simulate button clickbutton.clicked();return 0;
}在这个示例中Button 类有一个 clicked 信号Dialog 类有一个 handleButtonClick 槽。通过 button.clicked.connect(dialog, Dialog::handleButtonClick)将按钮的 clicked 信号连接到对话框的 handleButtonClick 槽。当 button.clicked() 被调用时handleButtonClick 槽会被自动调用。
总结
sigslot 是一个轻量级且易于使用的信号和槽库适用于需要简单信号和槽机制的项目。虽然它的功能相对简单但对于许多应用场景来说已经足够。如果你正在寻找一个轻量级的解决方案sigslot 是一个值得考虑的选择。
附源码实现sigslot-1.2.2带中文注释
sigslot 源码实现了一个信号槽Signal-Slot机制这是一种用于实现对象间通信的设计模式。信号槽机制允许一个对象信号发送者在特定事件发生时通知其他对象槽接收者而无需知道这些对象的具体类型。这种解耦的设计使得系统更加灵活和可扩展。
#pragma once
#include atomic
#include cstring
#include memory
#include mutex
#include type_traits
#include utility
#include thread
#include vector#if defined(__GXX_RTTI) || defined(__cpp_rtti) || defined(_CPPRTTI)
#define SIGSLOT_RTTI_ENABLED 1
#include typeinfo
#endifnamespace sigslot {namespace detail {// 用于检测观察者类型的结构体
struct observer_type {};} // namespace detailnamespace trait {/// 表示类型列表的模板
template typename... struct typelist {};/*** 可以转换为弱指针概念的指针必须实现to_weak()函数以便使用ADL进行转换并使其可用*/template typename T
std::weak_ptrT to_weak(std::weak_ptrT w) {return w;
}template typename T
std::weak_ptrT to_weak(std::shared_ptrT s) {return s;
}// 工具
namespace detail {template typename...
struct voider { using type void; };// void_t from c17
template typename...T
using void_t typename detail::voiderT...::type;template typename, typename void
struct has_call_operator : std::false_type {};template typename F
struct has_call_operatorF, void_tdecltype(std::remove_referenceF::type::operator()): std::true_type {};template typename, typename, typename void, typename void
struct is_callable : std::false_type {};template typename F, typename P, typename... T
struct is_callableF, P, typelistT...,void_tdecltype(((*std::declvalP()).*std::declvalF())(std::declvalT()...)): std::true_type {};template typename F, typename... T
struct is_callableF, typelistT...,void_tdecltype(std::declvalF()(std::declvalT()...)): std::true_type {};template typename T, typename void
struct is_weak_ptr : std::false_type {};template typename T
struct is_weak_ptrT, void_tdecltype(std::declvalT().expired()),decltype(std::declvalT().lock()),decltype(std::declvalT().reset()): std::true_type {};template typename T, typename void
struct is_weak_ptr_compatible : std::false_type {};template typename T
struct is_weak_ptr_compatibleT, void_tdecltype(to_weak(std::declvalT())): is_weak_ptrdecltype(to_weak(std::declvalT())) {};} // namespace detailstatic constexpr bool with_rtti
#ifdef SIGSLOT_RTTI_ENABLEDtrue;
#elsefalse;
#endif/// 确定一个指针是否可以转换为“弱”指针
template typename P
constexpr bool is_weak_ptr_compatible_v detail::is_weak_ptr_compatiblestd::decay_tP::value;/// 确定类型T可调用或Pmf是否可以使用提供的参数调用
template typename L, typename... T
constexpr bool is_callable_v detail::is_callableT..., L::value;template typename T
constexpr bool is_weak_ptr_v detail::is_weak_ptrT::value;template typename T
constexpr bool has_call_operator_v detail::has_call_operatorT::value;template typename T
constexpr bool is_pointer_v std::is_pointerT::value;template typename T
constexpr bool is_func_v std::is_functionT::value;template typename T
constexpr bool is_pmf_v std::is_member_function_pointerT::value;template typename T
constexpr bool is_observer_v std::is_base_of::sigslot::detail::observer_type,std::remove_pointer_tstd::remove_reference_tT::value;} // namespace traittemplate typename, typename...
class signal_base;/*** 用于标识一组槽的group_id*/
using group_id std::int32_t;namespace detail {/*** 以下function_traits和object_pointer系列模板用于规避slot_base实现中发生的类型擦除。* 它们用于比较存储的函数和对象与另一个对象以便进行断开连接。*//** 函数指针和成员函数指针的大小因编译器而异对于虚拟成员与非虚拟成员也是如此。* 在某些编译器上多重继承也有影响。因此我们形成一个足够大的联合来存储任何类型的函数指针。*/
namespace mock {struct a { virtual ~a() default; void f(); virtual void g(); static void h(); };
struct b { virtual ~b() default; void f(); virtual void g(); };
struct c : a, b { void f(); void g() override; };
struct d : virtual a { void g() override; };union fun_types {decltype(d::g) dm;decltype(c::g) mm;decltype(c::g) mvm;decltype(a::f) m;decltype(a::g) vm;decltype(a::h) s;void (*f)();void *o;};} // namespace mock/** 用于存储函数指针的结构体。* 这对于通过函数指针断开槽连接是必需的。* 它假定底层实现是可平凡复制的。*/
struct func_ptr {func_ptr(): sz{0}{std::uninitialized_fill(std::begin(data), std::end(data), \0);}template typename Tvoid store(const T t) {const auto *b reinterpret_castconst char*(t);sz sizeof(T);std::memcpy(data, b, sz);}template typename Tconst T* as() const {if (sizeof(T) ! sz) {return nullptr;}return reinterpret_castconst T*(data);}private:alignas(sizeof(mock::fun_types)) char data[sizeof(mock::fun_types)];size_t sz;
};template typename T, typename void
struct function_traits {static void ptr(const T /*t*/, func_ptr /*d*/) {}static bool eq(const T /*t*/, const func_ptr /*d*/) {return false;}static constexpr bool is_disconnectable false;static constexpr bool must_check_object true;
};template typename T
struct function_traitsT, std::enable_if_ttrait::is_func_vT {static void ptr(T t, func_ptr d) {d.store(t);}static bool eq(T t, const func_ptr d) {const auto *r d.asconst T*();return r *r t;}static constexpr bool is_disconnectable true;static constexpr bool must_check_object false;
};template typename T
struct function_traitsT*, std::enable_if_ttrait::is_func_vT {static void ptr(T *t, func_ptr d) {function_traitsT::ptr(*t, d);}static bool eq(T *t, const func_ptr d) {return function_traitsT::eq(*t, d);}static constexpr bool is_disconnectable true;static constexpr bool must_check_object false;
};template typename T
struct function_traitsT, std::enable_if_ttrait::is_pmf_vT {static void ptr(T t, func_ptr d) {d.store(t);}static bool eq(T t, const func_ptr d) {const auto *r d.asconst T();return r *r t;}static constexpr bool is_disconnectable trait::with_rtti;static constexpr bool must_check_object true;
};// 对于函数对象假设我们在寻找调用运算符
template typename T
struct function_traitsT, std::enable_if_ttrait::has_call_operator_vT {using call_type decltype(std::remove_referenceT::type::operator());static void ptr(const T /*t*/, func_ptr d) {function_traitscall_type::ptr(T::operator(), d);}static bool eq(const T /*t*/, const func_ptr d) {return function_traitscall_type::eq(T::operator(), d);}static constexpr bool is_disconnectable function_traitscall_type::is_disconnectable;static constexpr bool must_check_object function_traitscall_type::must_check_object;
};template typename T
func_ptr get_function_ptr(const T t) {func_ptr d;function_traitsstd::decay_tT::ptr(t, d);return d;
}template typename T
bool eq_function_ptr(const T t, const func_ptr d) {return function_traitsstd::decay_tT::eq(t, d);
}/** obj_ptr用于存储指向对象的指针。* 需要对象指针特征来正确处理可跟踪对象因为它们可能不是指针。*/
using obj_ptr const void*;template typename T
obj_ptr get_object_ptr(const T t);template typename T, typename void
struct object_pointer {static obj_ptr get(const T) {return nullptr;}
};template typename T
struct object_pointerT*, std::enable_if_ttrait::is_pointer_vT* {static obj_ptr get(const T *t) {return reinterpret_castobj_ptr(t);}
};template typename T
struct object_pointerT, std::enable_if_ttrait::is_weak_ptr_vT {static obj_ptr get(const T t) {auto p t.lock();return get_object_ptr(p);}
};template typename T
struct object_pointerT, std::enable_if_t!trait::is_pointer_vT !trait::is_weak_ptr_vT trait::is_weak_ptr_compatible_vT
{static obj_ptr get(const T t) {return t ? reinterpret_castobj_ptr(t.get()) : nullptr;}
};template typename T
obj_ptr get_object_ptr(const T t) {return object_pointerT::get(t);
}// 用于线程不安全使用的空互斥锁
struct null_mutex {null_mutex() noexcept default;~null_mutex() noexcept default;null_mutex(const null_mutex ) delete;null_mutex operator(const null_mutex ) delete;null_mutex(null_mutex ) delete;null_mutex operator(null_mutex ) delete;inline bool try_lock() noexcept { return true; }inline void lock() noexcept {}inline void unlock() noexcept {}
};/*** 一个自旋互斥锁主要用于基准测试和在非常高速调用槽的场景中使用。* 通常应优先使用标准互斥锁。*/
struct spin_mutex {spin_mutex() noexcept default;~spin_mutex() noexcept default;spin_mutex(spin_mutex const) delete;spin_mutex operator(const spin_mutex ) delete;spin_mutex(spin_mutex ) delete;spin_mutex operator(spin_mutex ) delete;void lock() noexcept {while (true) {while (!state.load(std::memory_order_relaxed)) {std::this_thread::yield();}if (try_lock()) {break;}}}bool try_lock() noexcept {return state.exchange(false, std::memory_order_acquire);}void unlock() noexcept {state.store(true, std::memory_order_release);}private:std::atomicbool state {true};
};/*** 一个简单的写时复制容器用于提高多线程上下文中槽列表访问效率。*/
template typename T
class copy_on_write {struct payload {payload() default;template typename... Argsexplicit payload(Args ...args): value(std::forwardArgs(args)...){}std::atomicstd::size_t count{1};T value;};public:using element_type T;copy_on_write(): m_data(new payload){}template typename Uexplicit copy_on_write(U x, std::enable_if_t!std::is_samestd::decay_tU,copy_on_write::value* nullptr): m_data(new payload(std::forwardU(x))){}copy_on_write(const copy_on_write x) noexcept: m_data(x.m_data){m_data-count;}copy_on_write(copy_on_write x) noexcept: m_data(x.m_data){x.m_data nullptr;}~copy_on_write() {if (m_data (--m_data-count 0)) {delete m_data;}}copy_on_write operator(const copy_on_write x) noexcept {if (x ! this) {*this copy_on_write(x);}return *this;}copy_on_write operator(copy_on_write x) noexcept {auto tmp std::move(x);swap(*this, tmp);return *this;}element_type write() {if (!unique()) {*this copy_on_write(read());}return m_data-value;}const element_type read() const noexcept {return m_data-value;}friend inline void swap(copy_on_write x, copy_on_write y) noexcept {using std::swap;swap(x.m_data, y.m_data);}private:bool unique() const noexcept {return m_data-count 1;}private:payload *m_data;
};/*** 线程安全代码路径的特化*/
template typename T
const T cow_read(const T v) {return v;
}template typename T
const T cow_read(copy_on_writeT v) {return v.read();
}template typename T
T cow_write(T v) {return v;
}template typename T
T cow_write(copy_on_writeT v) {return v.write();
}/*** std::make_shared 实例化了很多模板使得编译时间和可执行文件大小远大于它们实际需要的。我们提供了一个等效的 make_shared* 函数它将避免大多数实例化但有以下权衡* - 不是异常安全的* - 分配了一个单独的控制块因此会使代码变慢。*/
#ifdef SIGSLOT_REDUCE_COMPILE_TIME
template typename B, typename D, typename ...Arg
inline std::shared_ptrB make_shared(Arg ... arg) {return std::shared_ptrB(static_castB*(new D(std::forwardArg(arg)...)));
}
#else
template typename B, typename D, typename ...Arg
inline std::shared_ptrB make_shared(Arg ... arg) {return std::static_pointer_castB(std::make_sharedD(std::forwardArg(arg)...));
}
#endif/* slot_state 持有与槽类型无关的状态用于通过 connection 和 scoped_connection 对象间接与槽交互。*/
class slot_state {
public:constexpr slot_state(group_id gid) noexcept: m_index(0), m_group(gid), m_connected(true), m_blocked(false){}virtual ~slot_state() default;virtual bool connected() const noexcept { return m_connected; }bool disconnect() noexcept {bool ret m_connected.exchange(false);if (ret) {do_disconnect();}return ret;}bool blocked() const noexcept { return m_blocked.load(); }void block() noexcept { m_blocked.store(true); }void unblock() noexcept { m_blocked.store(false); }protected:virtual void do_disconnect() {}auto index() const {return m_index;}auto index() {return m_index;}group_id group() const {return m_group;}private:template typename, typename...friend class ::sigslot::signal_base;std::size_t m_index; // 信号内部槽指针数组的索引const group_id m_group; // 该槽所属的槽组std::atomicbool m_connected;std::atomicbool m_blocked;
};} // namespace detail/*** connection_blocker 是一个 RAII 对象它在销毁之前阻塞连接。*/
class connection_blocker {
public:connection_blocker() default;~connection_blocker() noexcept { release(); }connection_blocker(const connection_blocker ) delete;connection_blocker operator(const connection_blocker ) delete;connection_blocker(connection_blocker o) noexcept: m_state{std::move(o.m_state)}{}connection_blocker operator(connection_blocker o) noexcept {release();m_state.swap(o.m_state);return *this;}private:friend class connection;explicit connection_blocker(std::weak_ptrdetail::slot_state s) noexcept: m_state{std::move(s)}{if (auto d m_state.lock()) {d-block();}}void release() noexcept {if (auto d m_state.lock()) {d-unblock();}}private:std::weak_ptrdetail::slot_state m_state;
};/*** 一个 connection 对象允许与正在进行的槽连接进行交互。** 它允许常见的操作如连接阻塞和断开连接。* 注意connection 不是一个 RAII 对象不需要持有这样的对象来保持信号-槽连接的存活。*/
class connection {
public:connection() default;virtual ~connection() default;connection(const connection ) noexcept default;connection operator(const connection ) noexcept default;connection(connection ) noexcept default;connection operator(connection ) noexcept default;bool valid() const noexcept {return !m_state.expired();}bool connected() const noexcept {const auto d m_state.lock();return d d-connected();}bool disconnect() noexcept {auto d m_state.lock();return d d-disconnect();}bool blocked() const noexcept {const auto d m_state.lock();return d d-blocked();}void block() noexcept {if (auto d m_state.lock()) {d-block();}}void unblock() noexcept {if (auto d m_state.lock()) {d-unblock();}}connection_blocker blocker() const noexcept {return connection_blocker{m_state};}protected:template typename, typename... friend class signal_base;explicit connection(std::weak_ptrdetail::slot_state s) noexcept: m_state{std::move(s)}{}protected:std::weak_ptrdetail::slot_state m_state;
};/*** scoped_connection 是 connection 的 RAII 版本。* 它在销毁时断开槽与信号的连接。*/
class scoped_connection final : public connection {
public:scoped_connection() default;~scoped_connection() override {disconnect();}/*implicit*/ scoped_connection(const connection c) noexcept : connection(c) {}/*implicit*/ scoped_connection(connection c) noexcept : connection(std::move(c)) {}scoped_connection(const scoped_connection ) noexcept delete;scoped_connection operator(const scoped_connection ) noexcept delete;scoped_connection(scoped_connection o) noexcept: connection{std::move(o.m_state)}{}scoped_connection operator(scoped_connection o) noexcept {disconnect();m_state.swap(o.m_state);return *this;}private:template typename, typename... friend class signal_base;explicit scoped_connection(std::weak_ptrdetail::slot_state s) noexcept: connection{std::move(s)}{}
};/*** Observer 是一个基类用于对象的侵入式生命周期跟踪。** 这是可跟踪指针如 std::shared_ptr和通过保持连接对象在作用域内进行手动连接管理的替代方案。* 从该类派生允许在实例销毁时自动断开所有连接到任何信号的槽。*/
template typename Lockable
struct observer_base : private detail::observer_type {virtual ~observer_base() default;protected:/*** 断开所有连接到该对象的信号。** 为了避免在多线程上下文中对半销毁实例调用槽派生类应在它们的析构函数中调用此方法。* 这将确保在销毁之前进行适当的断开连接。*/void disconnect_all() {std::unique_lockLockable _{m_mutex};m_connections.clear();}private:template typename, typename ...friend class signal_base;void add_connection(connection conn) {std::unique_lockLockable _{m_mutex};m_connections.emplace_back(std::move(conn));}Lockable m_mutex;std::vectorscoped_connection m_connections;
};/*** observer_base 的特化用于单线程上下文。*/
using observer_st observer_basedetail::null_mutex;/*** observer_base 的特化用于多线程上下文。*/
using observer observer_basestd::mutex;namespace detail {// 用于清理断开连接槽的可清理对象接口
struct cleanable {virtual ~cleanable() default;virtual void clean(slot_state *) 0;
};template typename...
class slot_base;template typename... T
using slot_ptr std::shared_ptrslot_baseT...;/* 槽对象的基类。该基类仅依赖于槽参数类型它将用作侵入式单链表中的一个元素因此具有公共的 next 成员。*/
template typename... Args
class slot_base : public slot_state {
public:using base_types trait::typelistArgs...;explicit slot_base(cleanable c, group_id gid): slot_state(gid), cleaner(c){}~slot_base() override default;// 方法实际上负责在发射发生时调用带有提供参数的“槽”函数。virtual void call_slot(Args...) 0;template typename... Uvoid operator()(U ...u) {if (slot_state::connected() !slot_state::blocked()) {call_slot(std::forwardU(u)...);}}// 检查我们是否存储了可调用对象 ctemplate typename Cbool has_callable(const C c) const {auto p get_callable();return eq_function_ptr(c, p);}template typename Cstd::enable_if_tfunction_traitsC::must_check_object, boolhas_full_callable(const C c) const {return has_callable(c) check_class_typestd::decay_tC();}template typename Cstd::enable_if_t!function_traitsC::must_check_object, boolhas_full_callable(const C c) const {return has_callable(c);}// 检查我们是否存储了对象 otemplate typename Obool has_object(const O o) const {return get_object() get_object_ptr(o);}protected:void do_disconnect() final {cleaner.clean(this);}// 检索嵌入在槽中的对象指针virtual obj_ptr get_object() const noexcept {return nullptr;}// 检索嵌入在槽中的可调用对象指针virtual func_ptr get_callable() const noexcept {return get_function_ptr(nullptr);}#ifdef SIGSLOT_RTTI_ENABLED// 检索嵌入在槽中的可调用对象类型信息virtual const std::type_info get_callable_type() const noexcept {return typeid(nullptr);}private:template typename Ubool check_class_type() const {return typeid(U) get_callable_type();}#elsetemplate typename Ubool check_class_type() const {return false;}
#endifprivate:cleanable cleaner;
};/** 一个槽对象持有状态信息以及一个可调用对象当其基类slot_base的函数调用运算符被调用时该可调用对象将被调用。*/
template typename Func, typename... Args
class slot final : public slot_baseArgs... {
public:template typename F, typename Gidconstexpr slot(cleanable c, F f, Gid gid): slot_baseArgs...(c, gid), func{std::forwardF(f)} {}protected:void call_slot(Args ...args) override {func(args...);}func_ptr get_callable() const noexcept override {return get_function_ptr(func);}#ifdef SIGSLOT_RTTI_ENABLEDconst std::type_info get_callable_type() const noexcept override {return typeid(func);}
#endifprivate:std::decay_tFunc func;
};/** 一种变体槽在可调用对象前添加一个连接对象*/
template typename Func, typename... Args
class slot_extended final : public slot_baseArgs... {
public:template typename Fconstexpr slot_extended(cleanable c, F f, group_id gid): slot_baseArgs...(c, gid), func{std::forwardF(f)} {}connection conn;protected:void call_slot(Args ...args) override {func(conn, args...);}func_ptr get_callable() const noexcept override {return get_function_ptr(func);}#ifdef SIGSLOT_RTTI_ENABLEDconst std::type_info get_callable_type() const noexcept override {return typeid(func);}
#endifprivate:std::decay_tFunc func;
};/** 一个槽对象持有状态信息一个对象和一个成员函数指针当其基类slot_base的函数调用运算符被调用时该成员函数指针将被调用。*/
template typename Pmf, typename Ptr, typename... Args
class slot_pmf final : public slot_baseArgs... {
public:template typename F, typename Pconstexpr slot_pmf(cleanable c, F f, P p, group_id gid): slot_baseArgs...(c, gid), pmf{std::forwardF(f)}, ptr{std::forwardP(p)} {}protected:void call_slot(Args ...args) override {((*ptr).*pmf)(args...);}func_ptr get_callable() const noexcept override {return get_function_ptr(pmf);}obj_ptr get_object() const noexcept override {return get_object_ptr(ptr);}#ifdef SIGSLOT_RTTI_ENABLEDconst std::type_info get_callable_type() const noexcept override {return typeid(pmf);}
#endifprivate:std::decay_tPmf pmf;std::decay_tPtr ptr;
};/** 一种变体槽在可调用对象前添加一个连接对象*/
template typename Pmf, typename Ptr, typename... Args
class slot_pmf_extended final : public slot_baseArgs... {
public:template typename F, typename Pconstexpr slot_pmf_extended(cleanable c, F f, P p, group_id gid): slot_baseArgs...(c, gid), pmf{std::forwardF(f)}, ptr{std::forwardP(p)} {}connection conn;protected:void call_slot(Args ...args) override {((*ptr).*pmf)(conn, args...);}func_ptr get_callable() const noexcept override {return get_function_ptr(pmf);}obj_ptr get_object() const noexcept override {return get_object_ptr(ptr);}#ifdef SIGSLOT_RTTI_ENABLEDconst std::type_info get_callable_type() const noexcept override {return typeid(pmf);}
#endifprivate:std::decay_tPmf pmf;std::decay_tPtr ptr;
};/** 一种实现槽的方式通过弱指针跟踪提供的对象的生命周期以便在该对象销毁时自动断开槽。*/
template typename Func, typename WeakPtr, typename... Args
class slot_tracked final : public slot_baseArgs... {
public:template typename F, typename Pconstexpr slot_tracked(cleanable c, F f, P p, group_id gid): slot_baseArgs...(c, gid), func{std::forwardF(f)}, ptr{std::forwardP(p)}{}bool connected() const noexcept override {return !ptr.expired() slot_state::connected();}protected:void call_slot(Args ...args) override {auto sp ptr.lock();if (!sp) {slot_state::disconnect();return;}if (slot_state::connected()) {func(args...);}}func_ptr get_callable() const noexcept override {return get_function_ptr(func);}obj_ptr get_object() const noexcept override {return get_object_ptr(ptr);}#ifdef SIGSLOT_RTTI_ENABLEDconst std::type_info get_callable_type() const noexcept override {return typeid(func);}
#endifprivate:std::decay_tFunc func;std::decay_tWeakPtr ptr;
};/** 一种实现槽的方式作为成员函数指针通过弱指针跟踪提供的对象的生命周期以便在该对象销毁时自动断开槽。*/
template typename Pmf, typename WeakPtr, typename... Args
class slot_pmf_tracked final : public slot_baseArgs... {
public:template typename F, typename Pconstexpr slot_pmf_tracked(cleanable c, F f, P p, group_id gid): slot_baseArgs...(c, gid), pmf{std::forwardF(f)}, ptr{std::forwardP(p)}{}bool connected() const noexcept override {return !ptr.expired() slot_state::connected();}protected:void call_slot(Args ...args) override {auto sp ptr.lock();if (!sp) {slot_state::disconnect();return;}if (slot_state::connected()) {((*sp).*pmf)(args...);}}func_ptr get_callable() const noexcept override {return get_function_ptr(pmf);}obj_ptr get_object() const noexcept override {return get_object_ptr(ptr);}#ifdef SIGSLOT_RTTI_ENABLEDconst std::type_info get_callable_type() const noexcept override {return typeid(pmf);}
#endifprivate:std::decay_tPmf pmf;std::decay_tWeakPtr ptr;
};} // namespace detail/*** signal_base 是观察者模式的一种实现通过使用一个发射对象和连接到信号的槽当信号发射时槽会被调用并传递提供的参数。** signal_base 是通用实现其锁定策略必须设置以决定线程安全保证。signal 和 signal_st 是多线程和单线程使用的部分特化。** 它不允许槽返回值。** 槽的执行顺序可以通过分配组ID来约束。同一组中的槽的执行顺序未指定不应依赖但组按组ID升序执行。当未设置槽的组ID时它被分配到组0。组ID可以是32位有符号整数的任何值。** tparam Lockable 决定锁定策略的锁类型* tparam T... 发射和槽函数的参数类型。*/
template typename Lockable, typename... T
class signal_base final : public detail::cleanable {template typename Lusing is_thread_safe std::integral_constantbool, !std::is_sameL, detail::null_mutex::value;template typename U, typename Lusing cow_type std::conditional_tis_thread_safeL::value,detail::copy_on_writeU, U;template typename U, typename Lusing cow_copy_type std::conditional_tis_thread_safeL::value,detail::copy_on_writeU, const U;using lock_type std::unique_lockLockable;using slot_base detail::slot_baseT...;using slot_ptr detail::slot_ptrT...;using slots_type std::vectorslot_ptr;struct group_type { slots_type slts; group_id gid; };using list_type std::vectorgroup_type; // 按组ID升序保持有序public:using arg_list trait::typelistT...;using ext_arg_list trait::typelistconnection, T...;signal_base() noexcept : m_block(false) {}~signal_base() override {disconnect_all();}signal_base(const signal_base) delete;signal_base operator(const signal_base) delete;signal_base(signal_base o) /* not noexcept */: m_block{o.m_block.load()}{lock_type lock(o.m_mutex);using std::swap;swap(m_slots, o.m_slots);}signal_base operator(signal_base o) /* not noexcept */ {lock_type lock1(m_mutex, std::defer_lock);lock_type lock2(o.m_mutex, std::defer_lock);std::lock(lock1, lock2);using std::swap;swap(m_slots, o.m_slots);m_block.store(o.m_block.exchange(m_block.load()));return *this;}/*** 发射信号** 效果所有未阻塞且连接的槽函数将被调用并传递提供的参数。* 安全性通过适当的锁定参见pal::signal可以从多个线程同时发射。保证仅适用于信号对象不涵盖槽函数中可能使用的共享状态的线程安全。** param a... 发射的参数*/template typename... Uvoid operator()(U ...a) {if (m_block) {return;}// 引用要执行的槽如果另一个线程写入可能会发生复制cow_copy_typelist_type, Lockable ref slots_reference();for (const auto group : detail::cow_read(ref)) {for (const auto s : group.slts) {s-operator()(a...);}}}/*** 连接一个参数兼容的可调用对象** 效果创建并存储一个新的槽负责在每次后续信号发射时执行提供的可调用对象。* 安全性线程安全性取决于锁定策略。** param c 可调用对象* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/template typename Callablestd::enable_if_ttrait::is_callable_varg_list, Callable, connectionconnect(Callable c, group_id gid 0) {using slot_t detail::slotCallable, T...;auto s make_slotslot_t(std::forwardCallable(c), gid);connection conn(s);add_slot(std::move(s));return conn;}/*** 连接一个带有额外连接参数的可调用对象** 可调用对象的第一个参数必须是连接类型。此重载允许可调用对象通过此参数管理其自己的连接。** param c 可调用对象* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/template typename Callablestd::enable_if_ttrait::is_callable_vext_arg_list, Callable, connectionconnect_extended(Callable c, group_id gid 0) {using slot_t detail::slot_extendedCallable, T...;auto s make_slotslot_t(std::forwardCallable(c), gid);connection conn(s);std::static_pointer_castslot_t(s)-conn conn;add_slot(std::move(s));return conn;}/*** 连接一个从观察者派生的成员函数指针** param pmf 成员函数指针* param ptr 从观察者派生的对象指针* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/template typename Pmf, typename Ptrstd::enable_if_ttrait::is_callable_varg_list, Pmf, Ptr trait::is_observer_vPtr, connectionconnect(Pmf pmf, Ptr ptr, group_id gid 0) {using slot_t detail::slot_pmfPmf, Ptr, T...;auto s make_slotslot_t(std::forwardPmf(pmf), std::forwardPtr(ptr), gid);connection conn(s);add_slot(std::move(s));ptr-add_connection(conn);return conn;}/*** 连接一个成员函数指针** param pmf 成员函数指针* param ptr 对象指针* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/template typename Pmf, typename Ptrstd::enable_if_ttrait::is_callable_varg_list, Pmf, Ptr !trait::is_observer_vPtr !trait::is_weak_ptr_compatible_vPtr, connectionconnect(Pmf pmf, Ptr ptr, group_id gid 0) {using slot_t detail::slot_pmfPmf, Ptr, T...;auto s make_slotslot_t(std::forwardPmf(pmf), std::forwardPtr(ptr), gid);connection conn(s);add_slot(std::move(s));return conn;}/*** 连接一个带有额外连接参数的成员函数指针** param pmf 成员函数指针* param ptr 对象指针* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/template typename Pmf, typename Ptrstd::enable_if_ttrait::is_callable_vext_arg_list, Pmf, Ptr !trait::is_weak_ptr_compatible_vPtr, connectionconnect_extended(Pmf pmf, Ptr ptr, group_id gid 0) {using slot_t detail::slot_pmf_extendedPmf, Ptr, T...;auto s make_slotslot_t(std::forwardPmf(pmf), std::forwardPtr(ptr), gid);connection conn(s);std::static_pointer_castslot_t(s)-conn conn;add_slot(std::move(s));return conn;}/*** 连接的重载用于生命周期对象跟踪和自动断开连接** Ptr 必须可以通过实现 ADL 检测到的转换函数 to_weak() 转换为遵循弱指针概念的对象。** 此重载涵盖了成员函数指针和该类的可跟踪指针的情况。** 注意仅存储弱引用槽不会延长所提供对象的生命周期。** param pmf 成员函数指针* param ptr 可跟踪对象指针* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/
template typename Pmf, typename Ptr
std::enable_if_t!trait::is_callable_varg_list, Pmf trait::is_weak_ptr_compatible_vPtr, connection
connect(Pmf pmf, Ptr ptr, group_id gid 0) {using trait::to_weak;auto w to_weak(std::forwardPtr(ptr));using slot_t detail::slot_pmf_trackedPmf, decltype(w), T...;auto s make_slotslot_t(std::forwardPmf(pmf), w, gid);connection conn(s);add_slot(std::move(s));return conn;
}/*** 连接的重载用于生命周期对象跟踪和自动断开连接** Trackable 必须可以通过实现 ADL 检测到的转换函数 to_weak() 转换为遵循弱指针概念的对象。** 此重载涵盖了独立可调用对象和无关的可跟踪对象的情况。** 注意仅存储弱引用槽不会延长所提供对象的生命周期。** param c 可调用对象* param ptr 可跟踪对象指针* param gid 可用于排序槽执行的标识符* return 一个连接对象可用于与槽交互*/
template typename Callable, typename Trackable
std::enable_if_ttrait::is_callable_varg_list, Callable trait::is_weak_ptr_compatible_vTrackable, connection
connect(Callable c, Trackable ptr, group_id gid 0) {using trait::to_weak;auto w to_weak(std::forwardTrackable(ptr));using slot_t detail::slot_trackedCallable, decltype(w), T...;auto s make_slotslot_t(std::forwardCallable(c), w, gid);connection conn(s);add_slot(std::move(s));return conn;
}/*** 创建一个连接其持续时间与返回对象绑定* 使用与 connect 相同的语义*/
template typename... CallArgs
scoped_connection connect_scoped(CallArgs ...args) {return connect(std::forwardCallArgs(args)...);
}/*** 断开与可调用对象绑定的槽** 效果断开所有与参数中的可调用对象绑定的槽。* 安全性线程安全性取决于锁定策略。** 如果可调用对象是自由函数或静态成员函数此重载始终可用。然而对于成员函数指针、函数对象或引用lambda需要 RTTI因为 C 规范不要求成员函数指针是唯一的。** param c 可调用对象* return 断开的槽的数量*/
template typename Callable
std::enable_if_t(trait::is_callable_varg_list, Callable ||trait::is_callable_vext_arg_list, Callable ||trait::is_pmf_vCallable) detail::function_traitsCallable::is_disconnectable, size_t
disconnect(const Callable c) {return disconnect_if([] (const auto s) {return s-has_full_callable(c);});
}/*** 断开与对象绑定的槽** 效果断开所有与参数中的对象或可跟踪对象绑定的槽。* 安全性线程安全性取决于锁定策略。** 对象可以是指针或可跟踪对象。** param obj 对象* return 断开的槽的数量*/
template typename Obj
std::enable_if_t!trait::is_callable_varg_list, Obj !trait::is_callable_vext_arg_list, Obj !trait::is_pmf_vObj, size_t
disconnect(const Obj obj) {return disconnect_if([] (const auto s) {return s-has_object(obj);});
}/*** 断开同时与可调用对象和对象绑定的槽** 效果断开所有与参数中的可调用对象和对象绑定的槽。* 安全性线程安全性取决于锁定策略。** 对于裸指针可调用对象应为成员函数指针。如果 obj 是可跟踪的可以使用任何类型的可调用对象。** param c 可调用对象* param obj 对象* return 断开的槽的数量*/
template typename Callable, typename Obj
size_t disconnect(const Callable c, const Obj obj) {return disconnect_if([] (const auto s) {return s-has_object(obj) s-has_callable(c);});
}/*** 断开特定组中的槽** 效果断开参数中组ID中的所有槽。* 安全性线程安全性取决于锁定策略。** param gid 组ID* return 断开的槽的数量*/
size_t disconnect(group_id gid) {lock_type lock(m_mutex);for (auto group : detail::cow_write(m_slots)) {if (group.gid gid) {size_t count group.slts.size();group.slts.clear();return count;}}return 0;
}/*** 断开所有槽* 安全性线程安全性取决于锁定策略*/
void disconnect_all() {lock_type lock(m_mutex);clear();
}/*** 阻塞信号发射* 安全性线程安全*/
void block() noexcept {m_block.store(true);
}/*** 解除信号发射阻塞* 安全性线程安全*/
void unblock() noexcept {m_block.store(false);
}/*** 测试信号发射的阻塞状态*/
bool blocked() const noexcept {return m_block.load();
}/*** 获取连接的槽的数量* 安全性线程安全*/
size_t slot_count() noexcept {cow_copy_typelist_type, Lockable ref slots_reference();size_t count 0;for (const auto g : detail::cow_read(ref)) {count g.slts.size();}return count;
}protected:
/*** 移除断开的槽*/
void clean(detail::slot_state *state) override {lock_type lock(m_mutex);const auto idx state-index();const auto gid state-group();// 查找组for (auto group : detail::cow_write(m_slots)) {if (group.gid gid) {auto slts group.slts;// 确保我们有正确的槽以防并发清理if (idx slts.size() slts[idx] slts[idx].get() state) {std::swap(slts[idx], slts.back());slts[idx]-index() idx;slts.pop_back();}return;}}
}private:
// 用于获取槽的引用以进行读取
inline cow_copy_typelist_type, Lockable slots_reference() {lock_type lock(m_mutex);return m_slots;
}// 创建一个新的槽
template typename Slot, typename... A
inline auto make_slot(A ...a) {return detail::make_sharedslot_base, Slot(*this, std::forwardA(a)...);
}// 将槽添加到正确组的槽列表中
void add_slot(slot_ptr s) {const group_id gid s-group();lock_type lock(m_mutex);auto groups detail::cow_write(m_slots);// 查找组auto it groups.begin();while (it ! groups.end() it-gid gid) {it;}// 如果需要创建一个新的组if (it groups.end() || it-gid ! gid) {it groups.insert(it, {{}, gid});}// 添加槽s-index() it-slts.size();it-slts.push_back(std::move(s));
}// 如果条件发生断开槽
template typename Cond
size_t disconnect_if(Cond cond) {lock_type lock(m_mutex);auto groups detail::cow_write(m_slots);size_t count 0;for (auto group : groups) {auto slts group.slts;size_t i 0;while (i slts.size()) {if (cond(slts[i])) {std::swap(slts[i], slts.back());slts[i]-index() i;slts.pop_back();count;} else {i;}}}return count;
}// 在锁定状态下调用移除所有槽
void clear() {detail::cow_write(m_slots).clear();
}private:
Lockable m_mutex;
cow_typelist_type, Lockable m_slots;
std::atomicbool m_block;
};/*** signal_base 的特化用于单线程上下文。* 槽的连接、断开和信号发射不是线程安全的。* 与线程安全版本相比性能提升不显著因此不太有用。*/
template typename... T
using signal_st signal_basedetail::null_mutex, T...;/*** signal_base 的特化用于多线程上下文。* 槽的连接、断开和信号发射是线程安全的。** 还支持递归信号发射和发射循环。*/
template typename... T
using signal signal_basestd::mutex, T...;} // namespace sigslot
附关于QT的元对象系统
元对象系统Meta-Object System是Qt框架中的一个核心组件它提供了一种机制来支持运行时类型信息RTTIRuntime Type Information和动态交互。元对象系统使得Qt能够在程序运行时获取对象的类型信息并允许对象之间的动态通信这包括但不限于信号与槽机制。
元对象系统的主要特点包括 类型信息元对象系统为每个Qt对象提供了类型信息这使得程序能够在运行时识别对象的类类型。 对象构建Qt使用元对象系统来创建对象。这包括对象的构造函数调用和内存分配。 信号与槽元对象系统是信号与槽机制的基础。它允许Qt在运行时动态地连接信号和槽即使它们在不同的线程中也是如此。 属性系统Qt的属性系统允许开发者定义对象的属性并在运行时读取和修改这些属性。元对象系统提供了这些属性的注册和管理。 枚举器和方法元对象系统支持枚举器和方法的动态调用。这意味着可以在运行时查询对象支持的枚举类型和方法并调用这些方法。 动态属性元对象系统支持动态属性的概念允许在运行时添加、修改或删除属性。 复制和克隆元对象系统提供了对象复制和克隆的支持这在Qt的模型/视图编程中非常有用。 多态性元对象系统支持多态性允许通过基类指针或引用调用派生类的方法。 事件处理元对象系统在事件处理中也起着关键作用它允许对象接收和处理不同类型的事件。 插件系统Qt的插件系统依赖于元对象系统来动态加载和卸载插件。
元对象系统是Qt框架中非常强大的一个功能它为Qt的许多高级特性提供了支持包括但不限于信号与槽、属性系统、事件处理等。通过元对象系统Qt能够实现高度的灵活性和动态性使得开发者能够编写出更加强大和灵活的应用程序。
QT的信号与槽机制原理
Qt的信号与槽机制的实现原理是基于元对象系统(Meta-Object System, MOS)实现的。
Qt中的信号与槽机制是Qt框架的核心特性之一它提供了一种灵活、高效的事件通信机制使得各个组件之间能够进行松耦合的通信从而实现模块化、可维护性强的程序设计。这种机制基于事件驱动的编程模型通过信号和槽之间的连接实现了对象之间的通信。在Qt中信号和槽都是特殊的成员函数它们通过特定的宏来声明和定义。信号使用signals关键字声明槽使用slots关键字声明而且它们可以是任意的成员函数。
元对象系统(MOC)每个继承自QObject的类都会通过元对象编译器(MOC)进行处理。MOC会在编译时生成一个针对该类的元对象其中包含了该类的元信息如类名、父类信息、信号列表、槽列表等。信号和槽的声明与连接在类的定义中通过signals和slots关键字声明信号和槽函数。使用QObject::connect()函数建立信号与槽之间的连接时编译器会在背后调用元对象系统的相关函数将信号和槽的指针信息保存到一个连接表中。信号的发射与槽函数的调用当信号源对象发射信号时实际上是调用了一个由MOC自动生成的emit_signal()函数并传递了相应的参数。在这个函数内部会根据连接表找到与该信号相关联的槽函数并依次调用这些槽函数。当信号发射时与之连接的槽函数会被自动调用并传递相应的参数。这些槽函数被视为普通的成员函数因此可以直接通过函数指针进行调用。
通过元对象系统Qt可以在运行时实现信号和槽之间的连接和调用从而实现了信号槽机制的功能。这种机制在处理用户界面事件、实现回调机制等方面非常有效极大地增强了代码的灵活性和可维护性。
其他资源
https://zhuanlan.zhihu.com/p/652880307
sigslot库--一个简单的C消息框架-CSDN博客
深入剖析WebRTC事件机制之Sigslot-腾讯云开发者社区-腾讯云
https://zhuanlan.zhihu.com/p/615949772
Qt 信号与槽机制原理_qt信号与槽机制原理-CSDN博客