信号和槽机制的轻量级实现,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 设计简洁,易于理解和使用。

缺点

  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(“槽”) 方法。

源码实现

#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 {

// Used to detect an object of observer type
struct observer_type {};

} // namespace detail

namespace trait {

/// represent a list of types
template <typename...> struct typelist {};

/**
 * Pointers that can be converted to a weak pointer concept for tracking
 * purpose must implement the to_weak() function in order to make use of
 * ADL to convert that type and make it usable
 */

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;
}

// tools
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

/// determine if a pointer is convertible into a "weak" pointer
template <typename P>
constexpr bool is_weak_ptr_compatible_v = detail::is_weak_ptr_compatible<std::decay_t<P>>::value;

/// determine if a type T (Callable or Pmf) is callable with supplied arguments
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;

/**
 * A group_id is used to identify a group of slots
 */
using group_id = std::int32_t;

namespace detail {

/**
 * The following function_traits and object_pointer series of templates are
 * used to circumvent the type-erasing that takes place in the slot_base
 * implementations. They are used to compare the stored functions and objects
 * with another one for disconnection purpose.
 */

/*
 * Function pointers and member function pointers size differ from compiler to
 * compiler, and for virtual members compared to non virtual members. On some
 * compilers, multiple inheritance has an impact too. Hence, we form an union
 * big enough to store any kind of function pointer.
 */
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

/*
 * This struct is used to store function pointers.
 * This is needed for slot disconnection by function pointer.
 * It assumes the underlying implementation to be trivially copiable.
 */
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;
};

// for function objects, the assumption is that we are looking for the call operator
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 is used to store a pointer to an object.
 * The object_pointer traits are needed to handle trackable objects correctly,
 * as they are likely to not be pointers.
 */
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);
}


// noop mutex for thread-unsafe use
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 {}
};

/**
 * A spin mutex that yields, mostly for use in benchmarks and scenarii that invoke
 * slots at a very high pace.
 * One should almost always prefer a standard mutex over this.
 */
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};
};

/**
 * A simple copy on write container that will be used to improve slot lists
 * access efficiency in a multithreaded context.
 */
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;
};

/**
 * Specializations for thread-safe code path
 */
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 instantiates a lot a templates, and makes both compilation time
 * and executable size far bigger than they need to be. We offer a make_shared
 * equivalent that will avoid most instantiations with the following tradeoffs:
 * - Not exception safe,
 * - Allocates a separate control block, and will thus make the code slower.
 */
#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 holds slot type independent state, to be used to interact with
 * slots indirectly through connection and scoped_connection objects.
 */
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;     // index into the array of slot pointers inside the signal
    const group_id m_group;  // slot group this slot belongs to
    std::atomic<bool> m_connected;
    std::atomic<bool> m_blocked;
};

} // namespace detail

/**
 * connection_blocker is a RAII object that blocks a connection until destruction
 */
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;
};


/**
 * A connection object allows interaction with an ongoing slot connection
 *
 * It allows common actions such as connection blocking and disconnection.
 * Note that connection is not a RAII object, one does not need to hold one
 * such object to keep the signal-slot connection alive.
 */
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 is a RAII version of connection
 * It disconnects the slot from the signal upon destruction.
 */
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 is a base class for intrusive lifetime tracking of objects.
 *
 * This is an alternative to trackable pointers, such as std::shared_ptr,
 * and manual connection management by keeping connection objects in scope.
 * Deriving from this class allows automatic disconnection of all the slots
 * connected to any signal when an instance is destroyed.
 */
template <typename Lockable>
struct observer_base : private detail::observer_type {
    virtual ~observer_base() = default;

protected:
    /**
     * Disconnect all signals connected to this object.
     *
     * To avoid invocation of slots on a semi-destructed instance, which may happen
     * in multi-threaded contexts, derived classes should call this method in their
     * destructor. This will ensure proper disconnection prior to the destruction.
     */
    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;
};

/**
 * Specialization of observer_base to be used in single threaded contexts.
 */
using observer_st = observer_base<detail::null_mutex>;

/**
 * Specialization of observer_base to be used in multi-threaded contexts.
 */
using observer = observer_base<std::mutex>;


namespace detail {

// interface for cleanable objects, used to cleanup disconnected slots
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...>>;


/* A base class for slot objects. This base type only depends on slot argument
 * types, it will be used as an element in an intrusive singly-linked list of
 * slots, hence the public next member.
 */
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;

    // method effectively responsible for calling the "slot" function with
    // supplied arguments whenever emission happens.
    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)...);
        }
    }

    // check if we are storing callable 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);
    }

    // check if we are storing object 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);
    }

    // retieve a pointer to the object embedded in the slot
    virtual obj_ptr get_object() const noexcept {
        return nullptr;
    }

    // retieve a pointer to the callable embedded in the slot
    virtual func_ptr get_callable() const noexcept {
        return get_function_ptr(nullptr);
    }

#ifdef SIGSLOT_RTTI_ENABLED
    // retieve a pointer to the callable embedded in the slot
    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;
};

/*
 * A slot object holds state information, and a callable to to be called
 * whenever the function call operator of its slot_base base class is called.
 */
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;
};

/*
 * Variation of slot that prepends a connection object to the callable
 */
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;
};

/*
 * A slot object holds state information, an object and a pointer over member
 * function to be called whenever the function call operator of its slot_base
 * base class is called.
 */
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;
};

/*
 * Variation of slot that prepends a connection object to the callable
 */
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;
};

/*
 * An implementation of a slot that tracks the life of a supplied object
 * through a weak pointer in order to automatically disconnect the slot
 * on said object destruction.
 */
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;
};

/*
 * An implementation of a slot as a pointer over member function, that tracks
 * the life of a supplied object through a weak pointer in order to automatically
 * disconnect the slot on said object destruction.
 */
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 is an implementation of the observer pattern, through the use
 * of an emitting object and slots that are connected to the signal and called
 * with supplied arguments when a signal is emitted.
 *
 * signal_base is the general implementation, whose locking policy must be
 * set in order to decide thread safety guarantees. signal and signal_st
 * are partial specializations for multi-threaded and single-threaded use.
 *
 * It does not allow slots to return a value.
 *
 * Slot execution order can be constrained by assigning group ids to the slots.
 * The execution order of slots in a same group is unspecified and should not be
 * relied upon, however groups are executed in ascending group ids order. When
 * the group id of a slot is not set, it is assigned to the group 0. Group ids
 * can have any value in the range of signed 32 bit integers.
 *
 * @tparam Lockable a lock type to decide the lock policy
 * @tparam T... the argument types of the emitting and slots functions.
 */
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>;  // kept ordered by ascending gid

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;
    }

    /**
     * Emit a signal
     *
     * Effect: All non blocked and connected slot functions will be called
     *         with supplied arguments.
     * Safety: With proper locking (see pal::signal), emission can happen from
     *         multiple threads simultaneously. The guarantees only apply to the
     *         signal object, it does not cover thread safety of potentially
     *         shared state used in slot functions.
     *
     * @param a... arguments to emit
     */
    template <typename... U>
    void operator()(U && ...a) {
        if (m_block) {
            return;
        }

        // Reference to the slots to execute them out of the lock
        // a copy may occur if another thread writes to it.
        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...);
            }
        }
    }

    /**
     * Connect a callable of compatible arguments
     *
     * Effect: Creates and stores a new slot responsible for executing the
     *         supplied callable for every subsequent signal emission.
     * Safety: Thread-safety depends on locking policy.
     *
     * @param c a callable
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Connect a callable with an additional connection argument
     *
     * The callable's first argument must be of type connection. This overload
     * the callable to manage it's own connection through this argument.
     *
     * @param c a callable
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Overload of connect for pointers over member functions derived from
     * observer
     *
     * @param pmf a pointer over member function
     * @param ptr an object pointer derived from observer
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Overload of connect for pointers over member functions
     *
     * @param pmf a pointer over member function
     * @param ptr an object pointer
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Overload  of connect for pointer over member functions and
     *
     * @param pmf a pointer over member function
     * @param ptr an object pointer
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Overload of connect for lifetime object tracking and automatic disconnection
     *
     * Ptr must be convertible to an object following a loose form of weak pointer
     * concept, by implementing the ADL-detected conversion function to_weak().
     *
     * This overload covers the case of a pointer over member function and a
     * trackable pointer of that class.
     *
     * Note: only weak references are stored, a slot does not extend the lifetime
     * of a suppied object.
     *
     * @param pmf a pointer over member function
     * @param ptr a trackable object pointer
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Overload of connect for lifetime object tracking and automatic disconnection
     *
     * Trackable must be convertible to an object following a loose form of weak
     * pointer concept, by implementing the ADL-detected conversion function to_weak().
     *
     * This overload covers the case of a standalone callable and unrelated trackable
     * object.
     *
     * Note: only weak references are stored, a slot does not extend the lifetime
     * of a suppied object.
     *
     * @param c a callable
     * @param ptr a trackable object pointer
     * @param gid an identifier that can be used to order slot execution
     * @return a connection object that can be used to interact with the slot
     */
    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;
    }

    /**
     * Creates a connection whose duration is tied to the return object
     * Use the same semantics as connect
     */
    template <typename... CallArgs>
    scoped_connection connect_scoped(CallArgs && ...args) {
        return connect(std::forward<CallArgs>(args)...);
    }

    /**
     * Disconnect slots bound to a callable
     *
     * Effect: Disconnects all the slots bound to the callable in argument.
     * Safety: Thread-safety depends on locking policy.
     *
     * If the callable is a free or static member function, this overload is always
     * available. However, RTTI is needed for it to work for pointer to member
     * functions, function objects or and (references to) lambdas, because the
     * C++ spec does not mandate the pointers to member functions to be unique.
     *
     * @param c a callable
     * @return the number of disconnected slots
     */
    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);
        });
    }

    /**
     * Disconnect slots bound to this object
     *
     * Effect: Disconnects all the slots bound to the object or tracked object
     *         in argument.
     * Safety: Thread-safety depends on locking policy.
     *
     * The object may be a pointer or trackable object.
     *
     * @param obj an object
     * @return the number of disconnected slots
     */
    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);
        });
    }

    /**
     * Disconnect slots bound both to a callable and object
     *
     * Effect: Disconnects all the slots bound to the callable and object in argument.
     * Safety: Thread-safety depends on locking policy.
     *
     * For naked pointers, the Callable is expected to be a pointer over member
     * function. If obj is trackable, any kind of Callable can be used.
     *
     * @param c a callable
     * @param obj an object
     * @return the number of disconnected slots
     */
    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);
        });
    }

    /**
     * Disconnect slots in a particular group
     *
     * Effect: Disconnects all the slots in the group id in argument.
     * Safety: Thread-safety depends on locking policy.
     *
     * @param gid a group id
     * @return the number of disconnected slots
     */
    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;
    }

    /**
     * Disconnects all the slots
     * Safety: Thread safety depends on locking policy
     */
    void disconnect_all() {
        lock_type lock(m_mutex);
        clear();
    }

    /**
     * Blocks signal emission
     * Safety: thread safe
     */
    void block() noexcept {
        m_block.store(true);
    }

    /**
     * Unblocks signal emission
     * Safety: thread safe
     */
    void unblock() noexcept {
        m_block.store(false);
    }

    /**
     * Tests blocking state of signal emission
     */
    bool blocked() const noexcept {
        return m_block.load();
    }

    /**
     * Get number of connected slots
     * Safety: thread safe
     */
    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:
    /**
     * remove disconnected slots
     */
    void clean(detail::slot_state *state) override {
        lock_type lock(m_mutex);
        const auto idx = state->index();
        const auto gid = state->group();

        // find the group
        for (auto &group : detail::cow_write(m_slots)) {
            if (group.gid == gid) {
                auto &slts = group.slts;

                // ensure we have the right slot, in case of concurrent cleaning
                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:
    // used to get a reference to the slots for reading
    inline cow_copy_type<list_type, Lockable> slots_reference() {
        lock_type lock(m_mutex);
        return m_slots;
    }

    // create a new slot
    template <typename Slot, typename... A>
    inline auto make_slot(A && ...a) {
        return detail::make_shared<slot_base, Slot>(*this, std::forward<A>(a)...);
    }

    // add the slot to the list of slots of the right group
    void add_slot(slot_ptr &&s) {
        const group_id gid = s->group();

        lock_type lock(m_mutex);
        auto &groups = detail::cow_write(m_slots);

        // find the group
        auto it = groups.begin();
        while (it != groups.end() && it->gid < gid) {
            it++;
        }

        // create a new group if necessary
        if (it == groups.end() || it->gid != gid) {
            it = groups.insert(it, {{}, gid});
        }

        // add the slot
        s->index() = it->slts.size();
        it->slts.push_back(std::move(s));
    }

    // disconnect a slot if a condition occurs
    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;
    }

    // to be called under lock: remove all the slots
    void clear() {
        detail::cow_write(m_slots).clear();
    }

private:
    Lockable m_mutex;
    cow_type<list_type, Lockable> m_slots;
    std::atomic<bool> m_block;
};

/**
 * Specialization of signal_base to be used in single threaded contexts.
 * Slot connection, disconnection and signal emission are not thread-safe.
 * The performance improvement over the thread-safe variant is not impressive,
 * so this is not very useful.
 */
template <typename... T>
using signal_st = signal_base<detail::null_mutex, T...>;

/**
 * Specialization of signal_base to be used in multi-threaded contexts.
 * Slot connection, disconnection and signal emission are thread-safe.
 *
 * Recursive signal emission and emission cycles are supported too.
 */
template <typename... T>
using signal = signal_base<std::mutex, T...>;

} // namespace sigslot

简单使用

#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 是一个值得考虑的选择。

附关于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博客

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

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

相关文章

AWS CDN新增用户ip 地区 城市 响应头

1.需要自定义cdn缓存策略 这里的策略也是先复制之前的cdn策略哈 最后复制完了 全部新增这两条标头key CloudFront-Viewer-Country CloudFront-Viewer-City 2.然后新增cdn函数&#xff0c;应用你写的这个函数 function handler(event) {var request event.request;var respon…

全国农产品地理标志登记汇总表(截至2022年2月25日)

数据来源&#xff1a;自主整理 数据范围&#xff1a;省级层面 数据数量&#xff1a;3510条数据指标&#xff1a; 本数据展示了截至2022年2月25日的全国农产品地理标志登记汇总表&#xff0c;具体指标展示如下表&#xff1a; 序号 年份 产品名称 所在地域 证书持有人…

【每日刷题】Day81

【每日刷题】Day81 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 日期累加_牛客题霸_牛客网 (nowcoder.com) 2. 打印日期_牛客题霸_牛客网 (nowcoder.com) 3. 2956.…

分享两个性价比极高的SSR方案

最近总监提出我们公司运营的一个网站运营数据有点差&#xff0c;亟待提升该网站的SEO&#xff08;搜索引擎优化&#xff09;体验。不然自然流量着实有点少&#xff0c;全靠氪金买百度付费流量&#xff0c;成本太高&#xff0c;显然不太现实。但是当时技术选型的时候并未考虑到S…

【Linux】权限的管理和Linux上的一些工具

文章目录 权限管理chgrpchownumaskfile指令sudo指令 目录权限粘滞位Linux中的工具1.软件包管理器yum2.rzsz Linux开发工具vim 总结 权限管理 chgrp 功能&#xff1a;修改文件或目录的所属组 格式&#xff1a;chgrp [参数] 用户组名 文件名 常用选项&#xff1a;-R 递归修改文…

解析 Mira :基于 Web3,让先进的 AI 技术易于访问和使用

“Mira 平台正在以 Web3 的方式解决当前 AI 开发面临的复杂性问题&#xff0c;同时保护 AI 贡献者的权益&#xff0c;让他们可以自主拥有并货币化自己的模型、数据和应用&#xff0c;以使先进的 AI 技术更加易于访问和使用。” AI 代表着一种先进的生产力&#xff0c;它通过深…

UE4-初见虚幻引擎

一.创建自己的工程 1.启动 a.通过桌面双击图标来打开对应版本的虚幻引擎 b.通过EPIC启动器开启动虚幻引擎 2.选择或新建项目 ps:高版本虚幻编辑器可以打开低版本的虚幻项目&#xff0c;但是高版本虚幻的项目不可以由低版本的虚幻编辑器打开。 3. 选择要打开的项目 4.选择模版 选…

mindspore打卡第24天之LSTM+CRF序列标注

LSTMCRF序列标注 概述 序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。以命名实…

Host碰撞实验

目录 Host碰撞原理 Host碰撞判断技巧 Host碰撞检测方法 Host碰撞实验步骤 从攻击者的视角来进行资产的梳理&#xff0c;采用全端口扫描子域名收集的方式&#xff0c;识别所有的企业资产暴露面。但即使是这样&#xff0c;往往会因为配置错误或是未及时回收等原因&#xff0c…

C++ std::lock_guard和 std::unique_lock

二者都是 C 标准库中用于管理互斥锁&#xff08;mutex&#xff09;的 RAII&#xff08;Resource Acquisition Is Initialization&#xff09;机制的类。这些类可以确保互斥锁在构造时被获取&#xff0c;在析构时被释放&#xff0c;从而避免死锁和资源泄漏问题。不过&#xff0c…

【数据结构取经之路】二叉搜索树的实现

目录 前言 二叉搜索树 概念 性质 二叉搜索树的实现 结点的定义 插入 查找 删除 二叉搜索树完整代码 前言 首先&#xff0c;二叉搜索树是一种数据结构&#xff0c;了解二叉搜素树有助于理解map和set的特性。 二叉搜索树 概念 二叉搜索树又称二叉排序树&#xff0c…

服务器操作集合

服务器使用PC作为代理访问外网 1、PC上启动代理&#xff0c;比如nginx 下载nginx&#xff1a;http://nginx.org/en/download.html 修改配置文件&#xff0c;在conf下&#xff1a; http {include mime.types;default_type application/octet-stream; sendfile …

C++深度解析教程笔记12ok-继承,继承的构造与析构,同名覆盖

C深度解析教程笔记12 第43课 - 继承的概念和意义实验-类的组合实验-类的继承实验-子类与父类的构造顺序小结 第44课 - 继承中的访问级别实验-子类直接访问父类非公成员&#xff08;error&#xff09;实验-子类访问父类非公成员protected实验-复杂的例子bug 小结 第45课 - 不同的…

如何构建全生命周期的安全体系架构来确保容器的安全?

容器技术在云原生应用和微服务架构中得到了广泛应用&#xff0c;其轻量、灵活和高效的特点使其成为现代IT环境中的重要工具。然而&#xff0c;尽管容器带来了许多优势&#xff0c;但其安全性问题也不容忽视。接下来跟随博主一起探索如何构建全生命周期的安全体系架构以确保容器…

《算法笔记》总结No.7——二分(多例题详解版)

一.二分查找 目前有一个有序数列&#xff0c;举个例子&#xff0c;假设是1~1000&#xff0c;让我们去查找931这个数字&#xff0c;浅显且暴力的做法就是直接从头到尾遍历一遍&#xff0c;直到找到931为止。当n非常大&#xff0c;比如达到100w时&#xff0c;这是一个非常大的量级…

经纬恒润底盘控制产品R-EPS成功量产

近日&#xff0c;经纬恒润开发的齿条式电动助力转向系统R-EPS&#xff08;Rack-Electronic Power Steering&#xff09;搭载某新能源车企中高端MPV车型&#xff0c;成功量产落地。 该产品采用恒润Double Pinion/Rack平台级的软硬件方案&#xff0c;模块复用程度更高&#xff0c…

5.4 软件工程-系统设计

系统设计 - 概述 设计软件系统总体结构 数据结构及数据库设计 编写概要设计文档、评审 详细设计的基本任务 真题

DHCP服务、FTP服务

一、DHCP 1.1 DHCP是什么 DHCP&#xff08;Dynamic Host Configuration Protocol&#xff0c;动态主机配置协议&#xff09;是一种网络协议&#xff0c;用于自动分配 IP 地址和其他网络配置信息给网络中的设备 1.2 DHCP的好处 自动化: 减少了手动配置 IP 地址和网络参数的工…

pc端注册页面 密码校验规则

1.密码校验规则 格应包含大小写字母、数字和特殊符号,长度为8-20 var validateRetrievePassword (rule, value, callback) > {let reg /^(?.*[A-Za-z])(?.*\d)(?.*[~!#$%^&*()_<>?:"{},.\/\\;[\]])[A-Za-z\d~!#$%^&*()_<>?:"{},.\/\\;…

Linux系统下weblogic10.3.6版本打补丁步骤

linux系统 weblogic补丁压缩包&#xff1a;p35586779_1036_Generic.zip 链接&#xff1a;https://pan.baidu.com/s/1EEz_zPX-VHp5EU5LLxfxjQ 提取码&#xff1a;XXXX &#xff08;补丁压缩包中包含以下东西&#xff09; 打补丁步骤&#xff1a; 1.备份原weblogic(需要先确保服…