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 arguments
    sigslot::signal<> sig;

    // sigslot::signal will connect to any callable provided it has compatible
    // arguments. Here are diverse examples
    sig.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 > 70300
    auto gen_lambda = [](auto && ... /*a*/) { std::cout << "generic lambda\n"; };
    sig.connect(gen_lambda);
#endif
#endif

    // emit a signal
    sig();

    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 double
    void 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& arguments
    sigslot::signal<float, int, bool, std::string&> sig;

    // a Generic lambda that prints its arguments to stdout
    auto lambda_printer = [] (auto a, auto && ...args) {
        std::cout << a;
        (void)std::initializer_list<int>{
            ((void)(std::cout << " " << args), 1)...
        };
        std::cout << "\n";
    };

    // connect the slots
    foo 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 signal
    sig(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 slot
    button.clicked.connect(&dialog, &Dialog::handleButtonClick);

    // Simulate button click
    button.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>
#endif

namespace sigslot {

namespace detail {

// 用于检测观察者类型的结构体
struct observer_type {};

} // namespace detail

namespace 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 detail

static constexpr bool with_rtti =
#ifdef SIGSLOT_RTTI_ENABLED
        true;
#else
        false;
#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 trait

template <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)...);
        }
    }

    // 检查我们是否存储了可调用对象 c
    template <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);
    }

    // 检查我们是否存储了对象 o
    template <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();
    }

#else
    template <typename U>
    bool check_class_type() const {
        return false;
    }
#endif

private:
    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_ENABLED
    const std::type_info& get_callable_type() const noexcept override {
        return typeid(func);
    }
#endif

private:
    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_ENABLED
    const std::type_info& get_callable_type() const noexcept override {
        return typeid(func);
    }
#endif

private:
    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_ENABLED
    const std::type_info& get_callable_type() const noexcept override {
        return typeid(pmf);
    }
#endif

private:
    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_ENABLED
    const std::type_info& get_callable_type() const noexcept override {
        return typeid(pmf);
    }
#endif

private:
    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_ENABLED
    const std::type_info& get_callable_type() const noexcept override {
        return typeid(func);
    }
#endif

private:
    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_ENABLED
    const std::type_info& get_callable_type() const noexcept override {
        return typeid(pmf);
    }
#endif

private:
    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.mfbz.cn/a/802388.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于LSTM及其变体的回归预测

1 所用模型 代码中用到了以下模型&#xff1a; 1. LSTM&#xff08;Long Short-Term Memory&#xff09;&#xff1a;长短时记忆网络&#xff0c;是一种特殊的RNN&#xff08;循环神经网络&#xff09;&#xff0c;能够解决传统RNN在处理长序列时出现的梯度消失或爆炸的问题。L…

MBR40150FCT-ASEMI无人机专用MBR40150FCT

编辑&#xff1a;ll MBR40150FCT-ASEMI无人机专用MBR40150FCT 型号&#xff1a;MBR40150FCT 品牌&#xff1a;ASEMI 封装&#xff1a;TO-220F 批号&#xff1a;最新 最大平均正向电流&#xff08;IF&#xff09;&#xff1a;40A 最大循环峰值反向电压&#xff08;VRRM&a…

typeorm实体多对多关系指定表名与关联字段

表结构 user 用户表结构 course 课程表结构 user_course 用户课程表 (每个用户可以有多个课程, 每个课程可以有多个用户, 该表用以建立多对多关系) 实体 user.entity.ts Entity(user, { schema: test }) export class User {PrimaryGeneratedColumn({ type: int, name: id }…

江科大SPI教程听课笔记

原理部分我打算听江科大的课复习一下&#xff0c;代码部分工作大概率用HAL库敲了。 SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。 硬件资源方面需要四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO (Master Input Slave…

自定义组件--密码修改对话框(拿来即用型)

前言 一个完整的系统中用户登录功能是不可或缺的&#xff0c;因此用户密码的修改对于前端开发者而言也是工作的重要一环&#xff0c;密码修改分为两种情况&#xff1a;一是用户自身想更换密码&#xff1b;另一种是忘记密码只能选择更换密码。本文自定义了一个通用且常见的组件-…

IDEA快速生成项目树形结构图

下图用的IDEA工具&#xff0c;但我觉得WebStorm 应该也可以 文章目录 进入项目根目录下&#xff0c;进入cmd输入如下指令&#xff1a; 只有文件夹 tree . > list.txt 包括文件夹和文件 tree /f . > list.txt 还可以为相关包路径加上注释

【STM32嵌入式系统设计与开发---拓展】——1_9_1上拉输入和下拉输入

在使用GPIO引脚时&#xff0c;上拉输入和下拉输入的选择取决于外部电路的特性和应用需求。以下是它们各自的应用场景&#xff1a; 1、上拉输入&#xff08;Pull-up Input&#xff09; 用途: 当默认状态需要为高电平时。 避免引脚悬空&#xff08;floating&#xff09;导致的…

Three.JS 使用RGBELoader和CubeTextureLoader 添加环境贴图

导入RGBELoader模块&#xff1a; import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js"; 使用 addRGBEMappingk(environment, background,url) {rgbeLoader new RGBELoader();rgbeLoader.loadAsync(url).then((texture) > {//贴图模式 经纬…

MongoDB教程(八):mongoDB数据备份与恢复

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言MongoDB 备…

socket功能定义和一般模型

1. socket的功能定义 socket是为了使两个应用程序间进行数据交换而存在的一种技术&#xff0c;不仅可以使同一个主机上两个应用程序间可以交换数据&#xff0c;而且可以使网络上的不同主机间上的应用程序间进行通信。 2. 图解socket的服务端/客户端模型

深度学习落地实战:基于UNet实现血管瘤超声图像分割

前言 大家好&#xff0c;我是机长 本专栏将持续收集整理市场上深度学习的相关项目&#xff0c;旨在为准备从事深度学习工作或相关科研活动的伙伴&#xff0c;储备、提升更多的实际开发经验&#xff0c;每个项目实例都可作为实际开发项目写入简历&#xff0c;且都附带完整的代…

cpp 强制转换

一、static_cast static_cast 是 C 中的一个类型转换操作符&#xff0c;用于在类的层次结构中进行安全的向上转换&#xff08;从派生类到基类&#xff09;或进行不需要运行时类型检查的转换。它主要用于基本数据类型之间的转换、对象指针或引用的向上转换&#xff08;即从派生…

【Redis】集群

文章目录 一、集群是什么&#xff1f;二、 Redis集群分布式存储为什么redis集群的最大槽数是16384&#xff08;不太懂&#xff09;redis的集群主节点数量基本不可能超过1000个 三、 配置集群&#xff08;三主三从&#xff09;3.1 配置config文件3.2 启动六台redis3.2 通过redis…

铜管和铝管、铝管和铝管焊接操作介绍

一、部分品牌冰箱、空调采用铜铝管或铝铝管之间的连接方式&#xff0c;连接方式有以下两种&#xff1a; 1、洛克环&#xff1a;是方便简单的方式&#xff0c;但其需从德国采购&#xff0c;成本过于高昂而且采购周期长&#xff1b; 2、铜铝异种材料钎焊技术&#xff1a;国内可…

怎样在 PostgreSQL 中优化对大表的索引创建和维护的性能开销?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 怎样在 PostgreSQL 中优化对大表的索引创建和维护的性能开销&#xff1f;一、理解大表和索引的概念&am…

[C++]——同步异步日志系统(7)

同步异步日志系统 一、日志器管理模块&#xff08;单例模式&#xff09;1.1 对日志器管理器进行设计1.2 实现日志器管理类的各个功能1.3. 设计一个全局的日志器建造者1.4 测试日志器管理器的接口和全局建造者类 二、宏函数和全局接口设计2.1 新建一个.h,文件,文件里面放我们写的…

小欧吃苹果-OPPO 2024届校招正式批笔试题-数据开发(C卷)

在处理这个问题前&#xff0c;先看一个经典的贪心算法题目。信息学奥赛一本通&#xff08;C版&#xff09;在线评测系统http://ybt.ssoier.cn:8088/problem_show.php?pid1320 注意移动纸牌的贪心策略并不是题目中给出的移动次序&#xff1a;第1堆纸牌9<10&#xff0c;因为是…

几何相关计算

目录 一、 判断两个矩形是否相交 二、判断两条线段是否相交 三、判断点是否在多边形内 四、垂足计算 五、贝塞尔曲线 六、坐标系 一、 判断两个矩形是否相交 当矩形1的最大值比矩形2的最小值都小&#xff0c;那矩形1和矩形2一定不相交&#xff0c;其他同理。 struct Po…

【STM32】按键控制LED光敏传感器控制蜂鸣器(江科大)

一、按键控制LED LED.c #include "stm32f10x.h" // Device header/*** 函 数&#xff1a;LED初始化* 参 数&#xff1a;无* 返 回 值&#xff1a;无*/ void LED_Init(void) {/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENAB…

醇香之旅:探索红酒的无穷魅力

在浩渺的饮品世界里&#xff0c;红酒如同一颗璀璨的星辰&#xff0c;闪烁着诱人的光芒。它以其不同的醇香和深邃的韵味&#xff0c;吸引着无数人的目光。今天&#xff0c;就让我们一起踏上这场醇香之旅&#xff0c;探索雷盛红酒所带来的无穷魅力。 一、初识红酒的醇香 当我们…