c++信号和槽机制的轻量级实现,sigslot 库介绍及使用

Qt中的信号与槽机制很好用,然而只在Qt环境中。在现代 C++ 编程中,对象间的通信是一个核心问题。为了解决这个问题,许多库提供了信号和槽(Signals and Slots)机制。今天推荐分享一个轻量级的实现:sigslot 库。源码也很小,用来学习c++的新特性也是不错的选择。

介绍

sigslot 是一个轻量级的 C++ 信号和槽库,它提供了一种类型安全的机制来处理对象之间的通信。信号和槽机制允许对象在状态变化时通知其他对象,而无需直接调用它们的成员函数。这种机制有助于减少对象之间的耦合,使代码更易于维护和扩展。

仓库地址

你可以在 GitHub 上找到 sigslot 库的源码: sigslot GitHub 仓库

https://github.com/palacaze/sigslot

优缺点

优点

  1. 类型安全sigslot 提供了编译时的类型检查,确保信号和槽之间的参数类型匹配。
  2. 多线程支持sigslot 支持多线程环境,可以安全地在不同线程之间传递信号。
  3. 自动连接管理sigslot 会自动管理信号和槽之间的连接,当对象被销毁时,相关的连接也会自动断开。
  4. 灵活性sigslot 允许一个信号连接到多个槽,也允许一个槽连接到多个信号。
  5. 简单易用sigslot 的 API 设计简洁,易于理解和使用。

注意:该库需要c++工具链最低支持c++14标准。 

缺点

  1. 功能相对简单:相比于 Boost.Signals2 或 Qt 的信号和槽机制,sigslot 的功能较为简单,可能不适合需要复杂信号和槽机制的项目。
  2. 文档和社区支持有限:作为一个相对小众的库,sigslot 的文档和社区支持可能不如一些主流库那么丰富。

sigslot作用

Sigslot是信号(signal)和槽(slot)的结合,是一种用于处理C++对象通信的机制。信号是一个对象发出的事件或状态的通知,而槽则是响应信号并执行特定动作的函数。

Sigslot 的作用一句话表式就是为了解耦。例如,有两个类 A 和 B,如果 B 使用 A, 就必须在 B 类中写入与 A 类有关的代码。

使用Sigslot的主要原因包括:

  1. 解耦对象之间的通信:Sigslot可以帮助对象完全独立通信,减少对象之间的耦合度,提高程序的可维护性和可扩展性。
  2. 简化对象之间的交互:Sigslot可以让对象之间的交互变得更加灵活和简单,使得代码更易于阅读和维护。
  3. 支持事件驱动编程:Sigslot可以方便地实现事件驱动的编程模式,使得代码结构清晰,易于理解。

总的来说,Sigslot可以帮助简化C++对象之间的通信和交互,使得代码更加清晰和可维护。

实现原理

sigslot的原理其实非常简单,它就是一个变化的观察者模式。观察者模式如下所示:

观察者模式,首先让 Observer(“观察者”)对象 注册到 Subject(“被观察者”) 对象中。当 Subject 状态发生变化时,遍历所有注册到自己的 Observer 对象,并调用它们的 notify方法。

sigslot与观察者模式类似,它使用signal(“信号”)和slot("槽"),区别在于 signal 主动连接自己感兴趣的类及其方法,将它们保存到自己的列表中。当发射信号时,它遍历所有的连接,调用 slot(“槽”) 方法。

简单使用

#include "sigslot/signal.hpp"
#include <iostream>void 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?id=68071// 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 <string>struct 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... Ts>void operator()(T a, Ts && ...args) const {std::cout << a;(void)std::initializer_list<int>{((void)(std::cout << " " << std::forward<Ts>(args)), 1)...};std::cout << "\n";}
};int main() {// declare a signal with float, int, bool and string& argumentssigslot::signal<float, 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_list<int>{((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.hpp>class Button {
public:sigslot::signal<> clicked;
};class Dialog {
public:void handleButtonClick() {std::cout << "Button clicked!" << std::endl;}
};int main() {Button button;Dialog dialog;// Connect the button's clicked signal to the dialog's 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_ptr<T> to_weak(std::weak_ptr<T> w) {return w;
}template <typename T>
std::weak_ptr<T> to_weak(std::shared_ptr<T> s) {return s;
}// 工具
namespace detail {template <typename...>
struct voider { using type = void; };// void_t from c++17
template <typename...T>
using void_t = typename detail::voider<T...>::type;template <typename, typename = void>
struct has_call_operator : std::false_type {};template <typename F>
struct has_call_operator<F, void_t<decltype(&std::remove_reference<F>::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_callable<F, P, typelist<T...>,void_t<decltype(((*std::declval<P>()).*std::declval<F>())(std::declval<T>()...))>>: std::true_type {};template <typename F, typename... T>
struct is_callable<F, typelist<T...>,void_t<decltype(std::declval<F>()(std::declval<T>()...))>>: std::true_type {};template <typename T, typename = void>
struct is_weak_ptr : std::false_type {};template <typename T>
struct is_weak_ptr<T, void_t<decltype(std::declval<T>().expired()),decltype(std::declval<T>().lock()),decltype(std::declval<T>().reset())>>: std::true_type {};template <typename T, typename = void>
struct is_weak_ptr_compatible : std::false_type {};template <typename T>
struct is_weak_ptr_compatible<T, void_t<decltype(to_weak(std::declval<T>()))>>: is_weak_ptr<decltype(to_weak(std::declval<T>()))> {};} // 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_compatible<std::decay_t<P>>::value;/// 确定类型T(可调用或Pmf)是否可以使用提供的参数调用
template <typename L, typename... T>
constexpr bool is_callable_v = detail::is_callable<T..., L>::value;template <typename T>
constexpr bool is_weak_ptr_v = detail::is_weak_ptr<T>::value;template <typename T>
constexpr bool has_call_operator_v = detail::has_call_operator<T>::value;template <typename T>
constexpr bool is_pointer_v = std::is_pointer<T>::value;template <typename T>
constexpr bool is_func_v = std::is_function<T>::value;template <typename T>
constexpr bool is_pmf_v = std::is_member_function_pointer<T>::value;template <typename T>
constexpr bool is_observer_v = std::is_base_of<::sigslot::detail::observer_type,std::remove_pointer_t<std::remove_reference_t<T>>>::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 T>void store(const T &t) {const auto *b = reinterpret_cast<const char*>(&t);sz = sizeof(T);std::memcpy(data, b, sz);}template <typename T>const T* as() const {if (sizeof(T) != sz) {return nullptr;}return reinterpret_cast<const 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_traits<T, std::enable_if_t<trait::is_func_v<T>>> {static void ptr(T &t, func_ptr &d) {d.store(&t);}static bool eq(T &t, const func_ptr &d) {const auto *r = d.as<const T*>();return r && *r == &t;}static constexpr bool is_disconnectable = true;static constexpr bool must_check_object = false;
};template <typename T>
struct function_traits<T*, std::enable_if_t<trait::is_func_v<T>>> {static void ptr(T *t, func_ptr &d) {function_traits<T>::ptr(*t, d);}static bool eq(T *t, const func_ptr &d) {return function_traits<T>::eq(*t, d);}static constexpr bool is_disconnectable = true;static constexpr bool must_check_object = false;
};template <typename T>
struct function_traits<T, std::enable_if_t<trait::is_pmf_v<T>>> {static void ptr(T t, func_ptr &d) {d.store(t);}static bool eq(T t, const func_ptr &d) {const auto *r = d.as<const 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_traits<T, std::enable_if_t<trait::has_call_operator_v<T>>> {using call_type = decltype(&std::remove_reference<T>::type::operator());static void ptr(const T &/*t*/, func_ptr &d) {function_traits<call_type>::ptr(&T::operator(), d);}static bool eq(const T &/*t*/, const func_ptr &d) {return function_traits<call_type>::eq(&T::operator(), d);}static constexpr bool is_disconnectable = function_traits<call_type>::is_disconnectable;static constexpr bool must_check_object = function_traits<call_type>::must_check_object;
};template <typename T>
func_ptr get_function_ptr(const T &t) {func_ptr d;function_traits<std::decay_t<T>>::ptr(t, d);return d;
}template <typename T>
bool eq_function_ptr(const T& t, const func_ptr &d) {return function_traits<std::decay_t<T>>::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_pointer<T*, std::enable_if_t<trait::is_pointer_v<T*>>> {static obj_ptr get(const T *t) {return reinterpret_cast<obj_ptr>(t);}
};template <typename T>
struct object_pointer<T, std::enable_if_t<trait::is_weak_ptr_v<T>>> {static obj_ptr get(const T &t) {auto p = t.lock();return get_object_ptr(p);}
};template <typename T>
struct object_pointer<T, std::enable_if_t<!trait::is_pointer_v<T> &&!trait::is_weak_ptr_v<T> &&trait::is_weak_ptr_compatible_v<T>>>
{static obj_ptr get(const T &t) {return t ? reinterpret_cast<obj_ptr>(t.get()) : nullptr;}
};template <typename T>
obj_ptr get_object_ptr(const T &t) {return object_pointer<T>::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::atomic<bool> state {true};
};/*** 一个简单的写时复制容器,用于提高多线程上下文中槽列表访问效率。*/
template <typename T>
class copy_on_write {struct payload {payload() = default;template <typename... Args>explicit payload(Args && ...args): value(std::forward<Args>(args)...){}std::atomic<std::size_t> count{1};T value;};public:using element_type = T;copy_on_write(): m_data(new payload){}template <typename U>explicit copy_on_write(U && x, std::enable_if_t<!std::is_same<std::decay_t<U>,copy_on_write>::value>* = nullptr): m_data(new payload(std::forward<U>(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_write<T> &v) {return v.read();
}template <typename T>
T& cow_write(T &v) {return v;
}template <typename T>
T& cow_write(copy_on_write<T> &v) {return v.write();
}/*** std::make_shared 实例化了很多模板,使得编译时间和可执行文件大小远大于它们实际需要的。我们提供了一个等效的 make_shared* 函数,它将避免大多数实例化,但有以下权衡:* - 不是异常安全的,* - 分配了一个单独的控制块,因此会使代码变慢。*/
#ifdef SIGSLOT_REDUCE_COMPILE_TIME
template <typename B, typename D, typename ...Arg>
inline std::shared_ptr<B> make_shared(Arg && ... arg) {return std::shared_ptr<B>(static_cast<B*>(new D(std::forward<Arg>(arg)...)));
}
#else
template <typename B, typename D, typename ...Arg>
inline std::shared_ptr<B> make_shared(Arg && ... arg) {return std::static_pointer_cast<B>(std::make_shared<D>(std::forward<Arg>(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::atomic<bool> m_connected;std::atomic<bool> 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_ptr<detail::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_ptr<detail::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_ptr<detail::slot_state> s) noexcept: m_state{std::move(s)}{}protected:std::weak_ptr<detail::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_ptr<detail::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_lock<Lockable> _{m_mutex};m_connections.clear();}private:template <typename, typename ...>friend class signal_base;void add_connection(connection conn) {std::unique_lock<Lockable> _{m_mutex};m_connections.emplace_back(std::move(conn));}Lockable m_mutex;std::vector<scoped_connection> m_connections;
};/*** observer_base 的特化,用于单线程上下文。*/
using observer_st = observer_base<detail::null_mutex>;/*** observer_base 的特化,用于多线程上下文。*/
using observer = observer_base<std::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_ptr<slot_base<T...>>;/* 槽对象的基类。该基类仅依赖于槽参数类型,它将用作侵入式单链表中的一个元素,因此具有公共的 next 成员。*/
template <typename... Args>
class slot_base : public slot_state {
public:using base_types = trait::typelist<Args...>;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... U>void operator()(U && ...u) {if (slot_state::connected() && !slot_state::blocked()) {call_slot(std::forward<U>(u)...);}}// 检查我们是否存储了可调用对象 ctemplate <typename C>bool has_callable(const C &c) const {auto p = get_callable();return eq_function_ptr(c, p);}template <typename C>std::enable_if_t<function_traits<C>::must_check_object, bool>has_full_callable(const C &c) const {return has_callable(c) && check_class_type<std::decay_t<C>>();}template <typename C>std::enable_if_t<!function_traits<C>::must_check_object, bool>has_full_callable(const C &c) const {return has_callable(c);}// 检查我们是否存储了对象 otemplate <typename O>bool 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 U>bool check_class_type() const {return typeid(U) == get_callable_type();}#elsetemplate <typename U>bool check_class_type() const {return false;}
#endifprivate:cleanable &cleaner;
};/** 一个槽对象持有状态信息,以及一个可调用对象,当其基类slot_base的函数调用运算符被调用时,该可调用对象将被调用。*/
template <typename Func, typename... Args>
class slot final : public slot_base<Args...> {
public:template <typename F, typename Gid>constexpr slot(cleanable &c, F && f, Gid gid): slot_base<Args...>(c, gid), func{std::forward<F>(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_t<Func> func;
};/** 一种变体槽,在可调用对象前添加一个连接对象*/
template <typename Func, typename... Args>
class slot_extended final : public slot_base<Args...> {
public:template <typename F>constexpr slot_extended(cleanable &c, F && f, group_id gid): slot_base<Args...>(c, gid), func{std::forward<F>(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_t<Func> func;
};/** 一个槽对象持有状态信息,一个对象和一个成员函数指针,当其基类slot_base的函数调用运算符被调用时,该成员函数指针将被调用。*/
template <typename Pmf, typename Ptr, typename... Args>
class slot_pmf final : public slot_base<Args...> {
public:template <typename F, typename P>constexpr slot_pmf(cleanable &c, F && f, P && p, group_id gid): slot_base<Args...>(c, gid), pmf{std::forward<F>(f)}, ptr{std::forward<P>(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_t<Pmf> pmf;std::decay_t<Ptr> ptr;
};/** 一种变体槽,在可调用对象前添加一个连接对象*/
template <typename Pmf, typename Ptr, typename... Args>
class slot_pmf_extended final : public slot_base<Args...> {
public:template <typename F, typename P>constexpr slot_pmf_extended(cleanable &c, F && f, P && p, group_id gid): slot_base<Args...>(c, gid), pmf{std::forward<F>(f)}, ptr{std::forward<P>(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_t<Pmf> pmf;std::decay_t<Ptr> ptr;
};/** 一种实现槽的方式,通过弱指针跟踪提供的对象的生命周期,以便在该对象销毁时自动断开槽。*/
template <typename Func, typename WeakPtr, typename... Args>
class slot_tracked final : public slot_base<Args...> {
public:template <typename F, typename P>constexpr slot_tracked(cleanable &c, F && f, P && p, group_id gid): slot_base<Args...>(c, gid), func{std::forward<F>(f)}, ptr{std::forward<P>(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_t<Func> func;std::decay_t<WeakPtr> ptr;
};/** 一种实现槽的方式,作为成员函数指针,通过弱指针跟踪提供的对象的生命周期,以便在该对象销毁时自动断开槽。*/
template <typename Pmf, typename WeakPtr, typename... Args>
class slot_pmf_tracked final : public slot_base<Args...> {
public:template <typename F, typename P>constexpr slot_pmf_tracked(cleanable &c, F && f, P && p, group_id gid): slot_base<Args...>(c, gid), pmf{std::forward<F>(f)}, ptr{std::forward<P>(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_t<Pmf> pmf;std::decay_t<WeakPtr> 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 L>using is_thread_safe = std::integral_constant<bool, !std::is_same<L, detail::null_mutex>::value>;template <typename U, typename L>using cow_type = std::conditional_t<is_thread_safe<L>::value,detail::copy_on_write<U>, U>;template <typename U, typename L>using cow_copy_type = std::conditional_t<is_thread_safe<L>::value,detail::copy_on_write<U>, const U&>;using lock_type = std::unique_lock<Lockable>;using slot_base = detail::slot_base<T...>;using slot_ptr = detail::slot_ptr<T...>;using slots_type = std::vector<slot_ptr>;struct group_type { slots_type slts; group_id gid; };using list_type = std::vector<group_type>;  // 按组ID升序保持有序public:using arg_list = trait::typelist<T...>;using ext_arg_list = trait::typelist<connection&, 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... U>void operator()(U && ...a) {if (m_block) {return;}// 引用要执行的槽,如果另一个线程写入,可能会发生复制cow_copy_type<list_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 Callable>std::enable_if_t<trait::is_callable_v<arg_list, Callable>, connection>connect(Callable && c, group_id gid = 0) {using slot_t = detail::slot<Callable, T...>;auto s = make_slot<slot_t>(std::forward<Callable>(c), gid);connection conn(s);add_slot(std::move(s));return conn;}/*** 连接一个带有额外连接参数的可调用对象** 可调用对象的第一个参数必须是连接类型。此重载允许可调用对象通过此参数管理其自己的连接。** @param c 可调用对象* @param gid 可用于排序槽执行的标识符* @return 一个连接对象,可用于与槽交互*/template <typename Callable>std::enable_if_t<trait::is_callable_v<ext_arg_list, Callable>, connection>connect_extended(Callable && c, group_id gid = 0) {using slot_t = detail::slot_extended<Callable, T...>;auto s = make_slot<slot_t>(std::forward<Callable>(c), gid);connection conn(s);std::static_pointer_cast<slot_t>(s)->conn = conn;add_slot(std::move(s));return conn;}/*** 连接一个从观察者派生的成员函数指针** @param pmf 成员函数指针* @param ptr 从观察者派生的对象指针* @param gid 可用于排序槽执行的标识符* @return 一个连接对象,可用于与槽交互*/template <typename Pmf, typename Ptr>std::enable_if_t<trait::is_callable_v<arg_list, Pmf, Ptr> &&trait::is_observer_v<Ptr>, connection>connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) {using slot_t = detail::slot_pmf<Pmf, Ptr, T...>;auto s = make_slot<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(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 Ptr>std::enable_if_t<trait::is_callable_v<arg_list, Pmf, Ptr> &&!trait::is_observer_v<Ptr> &&!trait::is_weak_ptr_compatible_v<Ptr>, connection>connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) {using slot_t = detail::slot_pmf<Pmf, Ptr, T...>;auto s = make_slot<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(ptr), gid);connection conn(s);add_slot(std::move(s));return conn;}/*** 连接一个带有额外连接参数的成员函数指针** @param pmf 成员函数指针* @param ptr 对象指针* @param gid 可用于排序槽执行的标识符* @return 一个连接对象,可用于与槽交互*/template <typename Pmf, typename Ptr>std::enable_if_t<trait::is_callable_v<ext_arg_list, Pmf, Ptr> &&!trait::is_weak_ptr_compatible_v<Ptr>, connection>connect_extended(Pmf && pmf, Ptr && ptr, group_id gid = 0) {using slot_t = detail::slot_pmf_extended<Pmf, Ptr, T...>;auto s = make_slot<slot_t>(std::forward<Pmf>(pmf), std::forward<Ptr>(ptr), gid);connection conn(s);std::static_pointer_cast<slot_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_v<arg_list, Pmf> &&trait::is_weak_ptr_compatible_v<Ptr>, connection>
connect(Pmf && pmf, Ptr && ptr, group_id gid = 0) {using trait::to_weak;auto w = to_weak(std::forward<Ptr>(ptr));using slot_t = detail::slot_pmf_tracked<Pmf, decltype(w), T...>;auto s = make_slot<slot_t>(std::forward<Pmf>(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_t<trait::is_callable_v<arg_list, Callable> &&trait::is_weak_ptr_compatible_v<Trackable>, connection>
connect(Callable && c, Trackable && ptr, group_id gid = 0) {using trait::to_weak;auto w = to_weak(std::forward<Trackable>(ptr));using slot_t = detail::slot_tracked<Callable, decltype(w), T...>;auto s = make_slot<slot_t>(std::forward<Callable>(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::forward<CallArgs>(args)...);
}/*** 断开与可调用对象绑定的槽** 效果:断开所有与参数中的可调用对象绑定的槽。* 安全性:线程安全性取决于锁定策略。** 如果可调用对象是自由函数或静态成员函数,此重载始终可用。然而,对于成员函数指针、函数对象或(引用)lambda,需要 RTTI,因为 C++ 规范不要求成员函数指针是唯一的。** @param c 可调用对象* @return 断开的槽的数量*/
template <typename Callable>
std::enable_if_t<(trait::is_callable_v<arg_list, Callable> ||trait::is_callable_v<ext_arg_list, Callable> ||trait::is_pmf_v<Callable>) &&detail::function_traits<Callable>::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_v<arg_list, Obj> &&!trait::is_callable_v<ext_arg_list, Obj> &&!trait::is_pmf_v<Obj>, 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_type<list_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_type<list_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_shared<slot_base, Slot>(*this, std::forward<A>(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_type<list_type, Lockable> m_slots;
std::atomic<bool> m_block;
};/*** signal_base 的特化,用于单线程上下文。* 槽的连接、断开和信号发射不是线程安全的。* 与线程安全版本相比,性能提升不显著,因此不太有用。*/
template <typename... T>
using signal_st = signal_base<detail::null_mutex, T...>;/*** signal_base 的特化,用于多线程上下文。* 槽的连接、断开和信号发射是线程安全的。** 还支持递归信号发射和发射循环。*/
template <typename... T>
using signal = signal_base<std::mutex, T...>;} // namespace sigslot

附关于QT的元对象系统

元对象系统(Meta-Object System)是Qt框架中的一个核心组件,它提供了一种机制来支持运行时类型信息(RTTI,Runtime Type Information)和动态交互。元对象系统使得Qt能够在程序运行时获取对象的类型信息,并允许对象之间的动态通信,这包括但不限于信号与槽机制。

元对象系统的主要特点包括:

  1. 类型信息:元对象系统为每个Qt对象提供了类型信息,这使得程序能够在运行时识别对象的类类型。

  2. 对象构建:Qt使用元对象系统来创建对象。这包括对象的构造函数调用和内存分配。

  3. 信号与槽:元对象系统是信号与槽机制的基础。它允许Qt在运行时动态地连接信号和槽,即使它们在不同的线程中也是如此。

  4. 属性系统:Qt的属性系统允许开发者定义对象的属性,并在运行时读取和修改这些属性。元对象系统提供了这些属性的注册和管理。

  5. 枚举器和方法:元对象系统支持枚举器和方法的动态调用。这意味着可以在运行时查询对象支持的枚举类型和方法,并调用这些方法。

  6. 动态属性:元对象系统支持动态属性的概念,允许在运行时添加、修改或删除属性。

  7. 复制和克隆:元对象系统提供了对象复制和克隆的支持,这在Qt的模型/视图编程中非常有用。

  8. 多态性:元对象系统支持多态性,允许通过基类指针或引用调用派生类的方法。

  9. 事件处理:元对象系统在事件处理中也起着关键作用,它允许对象接收和处理不同类型的事件。

  10. 插件系统:Qt的插件系统依赖于元对象系统来动态加载和卸载插件。

元对象系统是Qt框架中非常强大的一个功能,它为Qt的许多高级特性提供了支持,包括但不限于信号与槽、属性系统、事件处理等。通过元对象系统,Qt能够实现高度的灵活性和动态性,使得开发者能够编写出更加强大和灵活的应用程序。

QT的信号与槽机制原理

Qt的信号与槽机制的实现原理是基于元对象系统(Meta-Object System, MOS)实现的。‌

Qt中的信号与槽机制是Qt框架的核心特性之一,‌它提供了一种灵活、‌高效的事件通信机制,‌使得各个组件之间能够进行松耦合的通信,‌从而实现模块化、‌可维护性强的程序设计。‌这种机制基于事件驱动的编程模型,‌通过信号和槽之间的连接,‌实现了对象之间的通信。‌在Qt中,‌信号和槽都是特殊的成员函数,‌它们通过特定的宏来声明和定义。‌信号使用signals关键字声明,‌槽使用slots关键字声明,‌而且它们可以是任意的成员函数。‌

  • 元对象系统(MOC):‌每个继承自QObject的类都会通过元对象编译器(MOC)进行处理。‌MOC会在编译时生成一个针对该类的元对象,‌其中包含了该类的元信息,‌如类名、‌父类信息、‌信号列表、‌槽列表等。‌
  • 信号和槽的声明与连接:‌在类的定义中,‌通过signalsslots关键字声明信号和槽函数。‌使用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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/48127.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【UE5.1】NPC人工智能——04 NPC巡逻

效果 步骤 一、准备行为树和黑板 1. 对我们之前创建的AI控制器创建一个子蓝图类 这里命名为“BP_NPC_AIController_Lion”&#xff0c;表示专门用于控制狮子的AI控制器 2. 打开狮子蓝图“Character_Lion” 在类默认值中将“AI控制器类”修改为“BP_NPC_AIController_Lion” 3…

web的运行

目录 1. web基础知识 1. http协议 2. 网络的三种架构及特点 1.客户机/服务器结构&#xff08;C/S&#xff09; 2. 浏览器/服务器结构&#xff08;B/S&#xff09; 3. P2P结构 3. 网站搭建 1. 服务器 2.中间件 4. 网站的运行原理 1. 网站的常用术语 1. 基本术语 2. …

《JavaSE》---16.<抽象类接口Object类>

目录 前言 一、抽象类 1.1什么是抽象类 1.2抽象类代码实现 1.3 抽象类特点 1.4抽象类的作用 二、接口 2.1什么是接口 2.2接口的代码书写 2.3 接口使用 2.4 接口特点 2.5 实现多个接口 快捷键&#xff08;ctrl i &#xff09;&#xff1a; 2.6接口的好处 2.7 接…

windows常用命令整理

本文分享一些常用的windows命令。根据功能的不同&#xff0c;大致可分为以下几个方面&#xff0c;一是文件操作命令&#xff0c;二是进程相关命令&#xff0c;三是磁盘相关命令&#xff0c;四是网络相关命令&#xff0c;五是其他命令。 1.文件操作命令 dir&#xff1a;显示当…

如何搭建一个RADIUS服务器?

1. 系统环境 1.1.操作系统 Ubuntu-20.04.1 &#xff08;kernel: 5.15.0-58-generic&#xff09; 1.2.所需软件 FreeRADIUS MariaDB 1.3.注意事项 本文提到的所有操作&#xff0c;都是以root 身份执行&#xff1b; 2. FreeRADIUS的安装 2.1. 安装FreeRADIUS服务器程序 以…

数据预处理在建模中的重要性与常见方法(三):特征工程篇

数据预处理在建模中的重要性与常见方法&#xff08;三&#xff09;&#xff1a;特征工程篇 特征工程是数据预处理中至关重要的一步&#xff0c;通过构建、转换和选择最能代表数据特性的特征&#xff0c;以提高模型的性能和准确性。常见的特征工程方法包括特征选择、特征提取和特…

零基础入门鸿蒙开发 HarmonyOS NEXT星河版开发学习

今天开始带大家零基础入门鸿蒙开发&#xff0c;也就是你没有任何编程基础的情况下就可以跟着石头哥零基础学习鸿蒙开发。 目录 一&#xff0c;为什么要学习鸿蒙 1-1&#xff0c;鸿蒙介绍 1-2&#xff0c;为什么要学习鸿蒙 1-3&#xff0c;鸿蒙各个版本介绍 1-4&#xff0…

P4-AI产品经理-九五小庞

从0开始做AI产品的完整工作方法 项目启动 项目实施 样本测试模型推荐引擎 构建DMP&#xff08;数据管理平台&#xff09; 项目上线

Leetcode双指针法应用

1.双指针法 文章目录 1.双指针法1.1什么是双指针法&#xff1f;1.2解题思路1.3扩展 1.1什么是双指针法&#xff1f; 双指针算法是一种在数组或序列上操作的技巧&#xff0c;实际上是对暴力枚举算法的一种优化&#xff0c;通常涉及到两个索引&#xff08;或指针&#xff09;从两…

springboot实现接口请求日志自动生成(日志自动埋点)

文章目录 1.作用&#xff1a;2.原理&#xff1a;3.代码&#xff1a;一.config层二. mq层 &#xff1a;三.service层&#xff1a; 4.效果图5.声明 1.作用&#xff1a; springboot接口请求日志自动生成&#xff0c;实现接口日志自动埋点生成 1.统一日志生成格式;—方便查看 2.汇…

19-4 LLM之野望 4 - 探索大模型的量化

什么是模型量化&#xff1f; 从本质上讲&#xff0c;模型量化就是为了提高效率。想象一下&#xff0c;你有一本非常厚的教科书&#xff08;就像那些老式百科全书一样&#xff09;&#xff0c;需要整天随身携带。很累吧&#xff1f;现在&#xff0c;如果你能把它缩小到一本漫画…

Postgresql导入几何数据的几种方式

postgis方式导入 1.直接使用postgis客户端方式导入 首先&#xff0c;电脑要安装postgresql和对应版本的postgis。然后通过postgis客户端软件连接到postgresql数据库。然后导入。具体详细操作如下所示&#xff1a; 第一步&#xff1a;首先要再postgis中创建数据库 Create da…

怎样在 PostgreSQL 中进行用户权限的精细管理?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中进行用户权限的精细管理&#xff1f;一、权限管理的重要性二、PostgreSQL 中的权…

Linux云计算 |【第一阶段】ENGINEER-DAY2

主要内容&#xff1a; 磁盘空间管理fdisk、parted工具、开机自动挂载、文件系统、交换空间 KVM虚拟化 实操前骤&#xff1a; 1&#xff09;添加一块硬盘&#xff08;磁盘&#xff09;&#xff0c;需要关机才能进行操作&#xff0c;点击左下角【添加硬件】 2&#xff09;选择2…

Lamp 小白菜鸟从入门到精通

前言 “LAMP包”的脚本组件中包括了CGIweb接口&#xff0c;它在90年代初期变得流行。这个技术允许网页浏览器的用户在服务器上执行一个程序&#xff0c;并且和接受静态的内容一样接受动态的内容。程序员使用脚本语言来创建这些程序因为它们能很容易有效的操作文本流&#xff0…

2.0.PyTorch神经网络基础

层和块 块&#xff08;block&#xff09;可以描述单个层、由多个层组成的组件或整个模型本身。 使用块进行抽象的一个好处是可以将一些块组合成更大的组件&#xff0c; 这一过程通常是递归的。多个层被组合成块&#xff0c;形成更大的模型&#xff1a; #层 import torch from …

LeetCode做题记录(第二天)169. 多数元素

题目&#xff1a;169. 多数元素 标签&#xff1a;数组 哈希表 分治 计数 排序 题目信息&#xff1a; 思路一&#xff1a; 在题目中出现了计数&#xff0c;那我们就可以直接考虑考虑使用哈希表 unordered_map 即遍历的时候记录每个数的出现次数&#xff0c;当出现次数大于n/…

苍穹外卖跟练项目前端localhost打不开页面启动nginx报错[alert] could not open error log file问题解决

一、安装路径为纯英文 查看自己的安装路径是否为纯英文环境&#xff0c;刚开始下载的资料包是有中文路径的&#xff0c;要将资料包中的nginx-1.20.2文件夹复制一份然后粘贴到一个新建的纯英文的目录&#xff0c;我这里装到的是 D:\Program Files\nginx-1.20.2 二、删掉logs文件…

【AI学习】关于Scaling Law的相关学习

一、苦涩的教训 首先&#xff0c;学习一段重要话语&#xff1a; The biggest lesson that can be read from 70 years of AI research is that general methods that leverage computation are ultimately the most effective, and by a large margin. 从70年的人工智能研究中…