c++信号和槽机制的轻量级实现,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++工具链最低支持c++14标准。
缺点
- 功能相对简单:相比于
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 <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能够在程序运行时获取对象的类型信息,并允许对象之间的动态通信,这包括但不限于信号与槽机制。
元对象系统的主要特点包括:
-
类型信息:元对象系统为每个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博客
相关文章:
c++信号和槽机制的轻量级实现,sigslot 库介绍及使用
Qt中的信号与槽机制很好用,然而只在Qt环境中。在现代 C 编程中,对象间的通信是一个核心问题。为了解决这个问题,许多库提供了信号和槽(Signals and Slots)机制。今天推荐分享一个轻量级的实现:sigslot 库。…...
云原生项目纪事系列 - 项目管理的鲜活事例
大规模云原生系统的新颖性、建设性和挑战性,吸引着许多有数学思想、哲学意识和美学观念的系统架构师,老模也是其中一员。 老模即是文史家庭出身,又有理工学业背景,他基于平时记录的翔实细节,秉持客观原则,使…...
【Vite】快速入门及其配置
概述 Vite是前端构建工具。vite 相较于webpack,vite采用了不同的运行方式: 开发时,并不对代码打包,而是直接采用ESM的方式来运行项目在项目打包部署时,使用 rollup 对项目进行打包除了速度外,vite使用起来也更加方便…...
Armv8/Armv9架构的学习大纲-学习方法-自学路线-付费学习路线
本文给大家列出了Arm架构的学习大纲、学习方法、自学路线、付费学习路线。有兴趣的可以关注,希望对您有帮助。 如果大家有需要的,欢迎关注我的CSDN课程:https://edu.csdn.net/lecturer/6964 ARM 64位架构介绍 ARM 64位架构介绍 ARM架构概况…...
vue 中 ui 组件二次封装后 ref 怎么穿透到子组件里
情景:element-ui 二次封装了 el-table 组件,使用封装组件时,想要调用 el-table 组件内置的一些方法。只在封装组件上定义 ref 是拿不到 el-table 内置方法的。解决方法如下。 1. vue2 封装组件 <template><el-table ref"inn…...
sourcetree中常用功能使用方法及gitlab冲突解决
添加至缓存:等于git add 提交:等于git commit 拉取/获取:等于git pull ,在每次要新增代码或者提交代码前需要先拉取一遍服务器中最新的代码,防止服务器有其他人更新了代码,但我们自己本地的代码在我们更新前跟服务器不…...
SQL Server分布式查询:跨数据库的无缝数据探索
SQL Server分布式查询:跨数据库的无缝数据探索 在当今的企业环境中,数据往往分散在不同的数据库和服务器上。SQL Server的分布式查询功能提供了一种强大的手段,允许用户编写单一的查询来访问和操作分散在不同SQL Server实例中的数据。本文将…...
【字少图多剖析微服务】深入理解Eureka核心原理
深入理解Eureka核心原理 Eureka整体设计Eureka服务端启动Eureka三级缓存Eureka客户端启动 Eureka整体设计 Eureka是一个经典的注册中心,通过http接收客户端的服务发现和服务注册请求,使用内存注册表保存客户端注册上来的实例信息。 Eureka服务端接收的…...
如何在 Linux 中解压 ZIP 文件
ZIP 是一种常用的压缩文件格式,用于存储和传输多个文件。在 Linux 系统中,解压 ZIP 文件非常简单。 使用 unzip 命令 unzip 是一个专用于解压 ZIP 文件的命令行工具。要使用它,请打开终端并输入以下命令: 例如,要解…...
IDEA的APIPost接口测试插件详解
APIPOST官方网址 一、安装APIPost插件 打开IntelliJ IDEA: 启动您的IntelliJ IDEA开发环境。 导航到插件设置: 在Windows或Linux上,点击 File > Settings。在macOS上,点击 IntelliJ IDEA > Preferences。 搜索并安装APIPo…...
[经验] 驰这个汉字的拼音是什么 #学习方法#其他#媒体
驰这个汉字的拼音是什么 驰,是一个常见的汉字,其拼音为“ch”,音调为第四声。它既可以表示动词,也可以表示形容词或副词,意义广泛,经常出现在生活和工作中。下面就让我们一起来了解一下“驰”的含义和用法。…...
生成式人工智能落地校园与课堂的15个场景
生成式人工智能正在重塑教育行业,为传统教学模式带来了革命性的变化。随着AI的不断演进,更多令人兴奋的应用场景将逐一显现,为学生提供更加丰富和多元的学习体验。 尽管AI在教学中的应用越来越广泛,但教师们也不必担心会被完全替代…...
C# 中的事件
1.事件的概念 在C#中,事件是一种特殊的委托类型,用于在对象之间提供一种基于观察者模式的通知机制。事件的发送方定义了一个委托,委托类型的声明包含了事件的签名,即事件处理器方法的签名。事件的订阅者可以通过运算符来注册事件…...
一、单例模式
文章目录 1 基本介绍2 实现方式2.1 饿汉式2.1.1 代码2.1.2 特性 2.2 懒汉式 ( 线程不安全 )2.2.1 代码2.2.2 特性 2.3 懒汉式 ( 线程安全 )2.3.1 代码2.3.2 特性 2.4 双重检查2.4.1 代码2.4.2 特性 2.5 静态内部类2.5.1 代码2.5.2 特性 2.6 枚举2.6.1 代码2.6.2 特性 3 实现的要…...
B树:高效的数据存储结构
在计算机科学中,B树(B-Tree)是一种平衡多路查找树,它广泛应用于数据库和文件系统等需要高效数据存储和检索的场景。B树的设计旨在优化磁盘I/O操作,通过减少磁盘访问次数来提高数据检索的效率。本文将介绍B树的基本概念…...
[Vulnhub] TORMENT IRC+FTP+CUPS+SMTP+apache配置文件权限提升+pkexec权限提升
信息收集 IP AddressOpening Ports192.168.101.152TCP:21,22,25,80,111,139,143,445,631 $ nmap -p- 192.168.101.152 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 2.0.8 or later | ftp-anon: Anonymous FTP login a…...
<数据集>安全帽佩戴识别数据集<目标检测>
数据集格式:VOCYOLO格式 图片数量:3912张 图片分辨率:640640 标注数量(xml文件个数):3912 标注数量(txt文件个数):3912 标注类别数:2 标注类别名称:[no-helmet, helmet] 序号类别名称图片…...
[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-21 VTC视频时序控制器设计
软件版本:Anlogic -TD5.9.1-DR1_ES1.1 操作系统:WIN10 64bit 硬件平台:适用安路(Anlogic)FPGA 实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板 板卡获取平台:https://milianke.tmall.com/ 登录“米联客”FPGA社区 ht…...
记录uni-app横屏项目:自定义弹出框
目录 前言: 正文: 前言:横屏的尺寸问题 最近使用了uniapp写了一个横屏的微信小程序和H5的项目,也是本人首次写的横屏项目,多少是有点踩坑不太适应。。。 先说最让我一脸懵的点,尺寸大小,下面一…...
Linux Vim教程(二):基本命令和操作
目录 1. 进入和退出Vim 1.1 启动Vim 1.2 退出Vim 2. 模式切换 2.1 切换到插入模式 2.2 切换到普通模式 2.3 切换到命令模式 2.4 切换到可视模式 3. 移动光标 4. 编辑文本 4.1 插入和追加文本 4.2 删除文本 4.3 复制和粘贴文本 4.4 撤销和重做 5. 搜索和替换 5.…...
【大模型基础】4.1 数据挖掘(待)
一、什么是文本挖掘? 文本挖掘指的是从文本数据中获取有价值的信息和知识,它是数据挖掘中的一种方法。文本挖掘中最重要最基本的应用是实现文本的分类和聚类,前者是有监督的挖掘算法,后者是无监督的挖掘算法。 二、文本挖掘的作用是什么? 能够从文本数据中获取有价值的…...
Jupyter Notebook与机器学习:使用Scikit-Learn构建模型
Jupyter Notebook与机器学习:使用Scikit-Learn构建模型 介绍 Jupyter Notebook是一款强大的交互式开发环境,广泛应用于数据科学和机器学习领域。Scikit-Learn是一个流行的Python机器学习库,提供了简单高效的工具用于数据挖掘和数据分析。本…...
IMU提升相机清晰度
近期,一项来自北京理工大学和北京师范大学的团队公布了一项创新性的研究成果,他们将惯性测量单元(IMU)和图像处理算法相结合,显著提升了非均匀相机抖动下图像去模糊的准确性。 研究团队利用IMU捕捉相机的运动数据&…...
掌握SQL Server性能监控:自定义性能计数器的实现
掌握SQL Server性能监控:自定义性能计数器的实现 在数据库管理中,监控数据库性能是确保系统稳定运行的关键。SQL Server提供了丰富的性能监控工具,但有时这些工具可能无法满足特定的监控需求。这时,自定义性能计数器就显得尤为重…...
jdk1.8 List集合Stream流式处理
jdk1.8 List集合Stream流式处理 一、介绍(为什么需要流Stream,能解决什么问题?)1.1 什么是 Stream?1.2 常见的创建Stream方法1.3 常见的中间操作1.4 常见的终端操作 二、创建流Stream2.1 Collection的.stream()方法2.2 数组创建流2.3 静态工厂…...
leetcode位运算(1720. 解码异或后的数组)
前言 经过前期的基础训练以及部分实战练习,粗略掌握了各种题型的解题思路。后续开始专项练习。 描述 未知 整数数组 arr 由 n 个非负整数组成。 经编码后变为长度为 n - 1 的另一个整数数组 encoded ,其中 encoded[i] arr[i] XOR arr[i 1] 。例如&am…...
Android 性能优化之卡顿优化
文章目录 Android 性能优化之卡顿优化卡顿检测TraceView配置缺点 StricktMode配置违规代码 BlockCanary配置问题代码缺点 ANRANR原因ANRWatchDog监测解决方案 Android 性能优化之卡顿优化 卡顿检测 TraceViewStricktModelBlockCanary TraceView 配置 Debug.startMethodTra…...
mac电脑显示隐藏文件
方法一: 第一步:打开「终端」应用程序。 第二步:输入如下命令: defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder 第三步:按下「Return」键确认。 现在你将会在 Finder 窗口中…...
深度学习之基础知识整理
现在大语言模型很火,但它的基础仍然是以神经网络为基础的深度学习,不懂神经网络,不了解深度学习,对于大语言模型的二次开发也是整不明白。 那到底需要了解哪些知识?才能看懂深度学习/神经网络的基础模型,想…...
R语言学习笔记9-数据过滤-分组-融合
R语言学习笔记9-数据过滤-分组-融合 数据过滤基础数据过滤条件筛选数据使用dplyr包进行数据操作select 函数filter 函数subset函数 数据分组使用split()进行数据分组使用dplyr包进行数据分组使用data.table包进行数据分组 数据融合使用merge()进行数据融合使用dplyr包进行数据融…...
网站建设找睿智骄阳/技成培训网
// 1 第一种 int pageCount rowCount/pageSize; if(rowCount%pageSize > 0){pageCount; }// 2 第二种 -- 推荐 int pageCount (rowCount pageSize - 1)/pageSize; 转载于:https://www.cnblogs.com/520future/p/7908331.html...
怎么做跟P站一样的网站/seo刷词
定时框架中最重要的就是时间,我们也可以直接使用Cron这种事件格式。 使用其他的时间格式,就可以用DateBuilder快速的创建出需要的时间。 因为quartz是一个定时框架,所以对于操控时间 需要更加准确的掌握。当时我们也可以直接通过BCL直接定义…...
容桂销售型网站建设/百度推广客户端手机版下载
1.管理工具---服务里面停止Mysql服务。 2.控制面板---卸载Mysql,删除C:\Program Files\MySQL目录. 3.这是最关键一步,只做前面两步,密码还是修改不了,因为MySQL 还有文件,也就是在C:\Documents and Settings\All Users…...
深圳网站建设公司选全通网络/百度广告屏蔽
注:本分类下文章大多整理自《深入分析linux内核源代码》一书,另有参考其他一些资料如《linux内核完全剖析》、《linux c 编程一站式学习》等,只是为了更好地理清系统编程和网络编程中的一些概念性问题,并没有深入地阅读分析源码&a…...
微信小程序二维码/seo 优化 工具
一、shell脚本的初识 二、shell脚本的创建与运行方式 三、shell脚本的变量定义 四、shell脚本的判断语句 五、shell脚本的循环语句 六、...
网站上的高清动态图怎么做的/百度推广在线客服
网上相当一部分博客如此描述内外部表的区别 创建表时:创建内部表时,会将数据移动到数据仓库指向的路径;若创建外部表,仅记录数据所在的路径, 不对数据的位置做任何改变。 删除表时:在删除表的时候ÿ…...