【链接】https://github.com/ispringtech/FastSignals
FastSignals 是一个 C++ 信号/槽(Signal/Slot)库,提供了线程安全的事件处理能力。类似于 Boost.Signals2 或 Qt 的信号槽机制,但专注于 高性能 和 低开销。它适用于需要高效事件处理的场景,比如游戏引擎、高频交易系统或嵌入式开发。
核心组件
-
信号类 (
signal.h
)-
实现了一对多事件通知的观察者模式
-
支持void和非void返回类型
-
使用组合器模式处理多个槽函数的返回值
-
提供连接管理和线程安全操作
-
-
连接管理 (
connection.h
)-
connection
: 基本连接句柄 -
advanced_connection
: 支持阻塞回调执行 -
scoped_connection
: 销毁时自动断开连接 -
shared_connection_block
: 临时阻塞槽函数执行
-
-
函数包装器 (
function.h
,function_detail.h
)-
高效替代
std::function
,带有小缓冲区优化 -
以最小开销支持各种可调用类型
-
提供类型安全的调用
-
-
线程安全
-
spin_mutex.h
: 轻量级同步原语 -
线程安全的断开连接操作
-
跨线程的安全信号调用
-
关键特性
-
内存安全
-
弱指针绑定(
bind_weak.h
)防止悬空引用 -
自动清理已断开的槽函数
-
小缓冲区优化实现高效槽函数存储
-
-
灵活的调用
-
支持const和非const成员函数
-
适当处理引用或值参数
-
可自定义非void槽函数的结果组合
-
-
性能优化
-
低竞争场景下的自旋锁
-
最小化模板膨胀
-
高效的槽函数存储和调用
-
-
高级功能
-
可阻塞的连接
-
信号到信号的连接
-
作用域连接管理
-
使用示例
基本信号/槽连接
is::signals::signal<void(int)> sig;
auto conn = sig.connect([](int x) { std::cout << x; });
sig(42); // 输出"42"
成员函数连接
struct MyClass {
void method(int x) { /*...*/ }
};
auto obj = std::make_shared<MyClass>();
is::signals::signal<void(int)> sig;
auto conn = sig.connect(is::signals::bind_weak(&MyClass::method, obj));
高级可阻塞连接
is::signals::signal<void()> sig;
auto adv_conn = sig.connect([](){ /*...*/ }, is::signals::advanced_tag{});
is::signals::shared_connection_block blocker(adv_conn);
sig(); // 阻塞时槽函数不会被调用
信号链式连接
is::signals::signal<void()> sig1, sig2;
sig1.connect(sig2); // 连接信号到信号
sig1(); // 同时会调用sig2
该库提供了一个健壮的、线程安全的信号和槽实现,特别关注内存安全和性能。设计上既支持简单用法也支持高级用法,同时保持高效性。
signal类
#pragma once
#include "combiners.h"
#include "connection.h"
#include "function.h"
#include "signal_impl.h"
#include "type_traits.h"
#include <type_traits>
#if defined(_MSC_VER)
# include "msvc_autolink.h"
#endif
namespace is::signals
{
template <class Signature, template <class T> class Combiner = optional_last_value>
class signal;
struct advanced_tag
{
};
/// Signal allows to fire events to many subscribers (slots).
/// In other words, it implements one-to-many relation between event and listeners.
/// Signal implements observable object from Observable pattern.
template <class Return, class... Arguments, template <class T> class Combiner>
class signal<Return(Arguments...), Combiner> : private not_directly_callable
{
public:
using signature_type = Return(signal_arg_t<Arguments>...);
using slot_type = function<signature_type>;
using combiner_type = Combiner<Return>;
using result_type = typename combiner_type::result_type;
signal()
: m_slots(std::make_shared<detail::signal_impl>())
{
}
/// No copy construction
signal(const signal&) = delete;
/// Moves signal from other. Any operations on other except destruction, move, and swap are invalid
signal(signal&& other) = default;
/// No copy assignment
signal& operator=(const signal&) = delete;
/// Moves signal from other. Any operations on other except destruction, move, and swap are invalid
signal& operator=(signal&& other) = default;
/**
* connect(slot) method subscribes slot to signal emission event.
* Each time you call signal as functor, all slots are also called with given arguments.
* @returns connection - object which manages signal-slot connection lifetime
*/
connection connect(slot_type slot)
{
const uint64_t id = m_slots->add(slot.release());
return connection(m_slots, id);
}
/**
* connect(slot, advanced_tag) method subscribes slot to signal emission event with the ability to temporarily block slot execution
* Each time you call signal as functor, all non-blocked slots are also called with given arguments.
* You can temporarily block slot execution using shared_connection_block
* @returns advanced_connection - object which manages signal-slot connection lifetime
*/
advanced_connection connect(slot_type slot, advanced_tag)
{
static_assert(std::is_void_v<Return>, "Advanced connect can only be used with slots returning void (implementation limitation)");
auto conn_impl = std::make_shared<advanced_connection::advanced_connection_impl>();
slot_type slot_impl = [this, slot, weak_conn_impl = std::weak_ptr(conn_impl)](signal_arg_t<Arguments>... args) {
auto conn_impl = weak_conn_impl.lock();
if (!conn_impl || !conn_impl->is_blocked())
{
slot(args...);
}
};
auto conn = connect(std::move(slot_impl));
return advanced_connection(std::move(conn), std::move(conn_impl));
}
/**
* disconnect_all_slots() method disconnects all slots from signal emission event.
*/
void disconnect_all_slots() noexcept
{
m_slots->remove_all();
}
/**
* num_slots() method returns number of slots attached to this singal
*/
[[nodiscard]] std::size_t num_slots() const noexcept
{
return m_slots->count();
}
/**
* empty() method returns true if signal has any slots attached
*/
[[nodiscard]] bool empty() const noexcept
{
return m_slots->count() == 0;
}
/**
* operator(args...) calls all slots connected to this signal.
* Logically, it fires signal emission event.
*/
result_type operator()(signal_arg_t<Arguments>... args) const
{
return detail::signal_impl_ptr(m_slots)->invoke<combiner_type, result_type, signature_type, signal_arg_t<Arguments>...>(args...);
}
void swap(signal& other) noexcept
{
m_slots.swap(other.m_slots);
}
/**
* Allows using signals as slots for another signal
*/
operator slot_type() const noexcept
{
return [weakSlots = detail::signal_impl_weak_ptr(m_slots)](signal_arg_t<Arguments>... args) {
if (auto slots = weakSlots.lock())
{
return slots->invoke<combiner_type, result_type, signature_type, signal_arg_t<Arguments>...>(args...);
}
};
}
private:
detail::signal_impl_ptr m_slots;
};
} // namespace is::signals
namespace std
{
// free swap function, findable by ADL
template <class Signature, template <class T> class Combiner>
void swap(
::is::signals::signal<Signature, Combiner>& sig1,
::is::signals::signal<Signature, Combiner>& sig2)
{
sig1.swap(sig2);
}
} // namespace std
signal
类实现了一个线程安全的信号/槽系统,是观察者模式的高效实现。
基本结构
template <class Signature, template <class T> class Combiner = optional_last_value>
class signal;
这是信号类的主模板声明,使用两个模板参数:
-
Signature
: 函数签名,表示信号和槽的类型 -
Combiner
: 组合器类型,默认为optional_last_value
信号类实现
template <class Return, class... Arguments, template <class T> class Combiner>
class signal<Return(Arguments...), Combiner> : private not_directly_callable
这是信号类的特化实现,继承自not_directly_callable
(防止直接调用)。
关键类型定义
using signature_type = Return(signal_arg_t<Arguments>...);
using slot_type = function<signature_type>;
using combiner_type = Combiner<Return>;
using result_type = typename combiner_type::result_type;
-
signature_type
: 信号和槽的函数签名 -
slot_type
: 槽函数类型,使用function
包装 -
combiner_type
: 组合器类型 -
result_type
: 组合器的结果类型
核心成员
detail::signal_impl_ptr m_slots;
这是信号的核心数据成员,使用共享指针管理槽函数实现。
主要功能方法
连接槽函数
connection connect(slot_type slot)
{
const uint64_t id = m_slots->add(slot.release());
return connection(m_slots, id);
}
-
将槽函数添加到信号中
-
返回一个
connection
对象管理连接生命周期
高级连接
advanced_connection connect(slot_type slot, advanced_tag)
{
// 实现略...
}
提供带阻塞功能的连接方式,可以临时阻止槽函数执行。
信号触发
result_type operator()(signal_arg_t<Arguments>... args) const
{
return detail::signal_impl_ptr(m_slots)->invoke<combiner_type, result_type, signature_type, signal_arg_t<Arguments>...>(args...);
}
调用信号时触发所有连接的槽函数,并使用组合器处理结果。
其他功能
void disconnect_all_slots() noexcept; // 断开所有槽
std::size_t num_slots() const noexcept; // 获取槽数量
bool empty() const noexcept; // 检查是否为空
void swap(signal& other) noexcept; // 交换信号
类型转换
operator slot_type() const noexcept
{
return[weakSlots = detail::signal_impl_weak_ptr(m_slots)](signal_arg_t<Arguments>... args) {
// 实现略...
};
}
允许将信号作为槽函数使用,实现信号链式连接。
示例
//这是一个类型别名定义,使用 using 关键字创建了一个名为CameraDataReady 的信号类型
using CameraDataReady = is::signals::signal<void(const std::vector<ImagePtr>&)>;
// 定义信号实例
CameraDataReady GrabDoneSignal;
// 连接槽函数
GrabDoneSignal.connect([](const auto& vecImages) {
std::cout << "Received " << vecImages.size() << " images\n";
for (const auto& result : vecImages) {
// 处理每个结果...
}
});
// 触发信号
std::vector<ImagePtr> vecImages= getVecImages();
GrabDoneSignal(defectResults);
signal_impl类
///signal_impl.h
#include "function_detail.h"
#include "spin_mutex.h"
#include <memory>
#include <vector>
namespace is::signals::detail
{
class signal_impl
{
public:
uint64_t add(packed_function fn);
void remove(uint64_t id) noexcept;
void remove_all() noexcept;
size_t count() const noexcept;
template <class Combiner, class Result, class Signature, class... Args>
Result invoke(Args... args) const
{
packed_function slot;
size_t slotIndex = 0;
uint64_t slotId = 1;
if constexpr (std::is_same_v<Result, void>)
{
while (get_next_slot(slot, slotIndex, slotId))
{
slot.get<Signature>()(std::forward<Args>(args)...);
}
}
else
{
Combiner combiner;
while (get_next_slot(slot, slotIndex, slotId))
{
combiner(slot.get<Signature>()(std::forward<Args>(args)...));
}
return combiner.get_value();
}
}
private:
bool get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const;
mutable spin_mutex m_mutex;
std::vector<packed_function> m_functions;
std::vector<uint64_t> m_ids;
uint64_t m_nextId = 1;
};
using signal_impl_ptr = std::shared_ptr<signal_impl>;
using signal_impl_weak_ptr = std::weak_ptr<signal_impl>;
} // namespace is::signals::detail
cpp文件
#include "../include/signal_impl.h"
#include <algorithm>
#include <mutex>
namespace is::signals::detail
{
uint64_t signal_impl::add(packed_function fn)
{
std::lock_guard lock(m_mutex);
m_functions.emplace_back(std::move(fn));
try
{
m_ids.emplace_back(m_nextId);
}
catch (const std::bad_alloc& /*e*/)
{
// Remove function since we failed to add its id
m_functions.pop_back();
throw;
}
return m_nextId++;
}
void signal_impl::remove(uint64_t id) noexcept
{
std::lock_guard lock(m_mutex);
// We use binary search because ids array is always sorted.
auto it = std::lower_bound(m_ids.begin(), m_ids.end(), id);
if (it != m_ids.end() && *it == id)
{
size_t i = std::distance(m_ids.begin(), it);
m_ids.erase(m_ids.begin() + i);
m_functions.erase(m_functions.begin() + i);
}
}
void signal_impl::remove_all() noexcept
{
std::lock_guard lock(m_mutex);
m_functions.clear();
m_ids.clear();
}
bool signal_impl::get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const
{
// Slots always arranged by ID, so we can use a simple algorithm which avoids races:
// - on each step find first slot with ID >= slotId
// - after each call increment slotId
std::lock_guard lock(m_mutex);
// Avoid binary search if next slot wasn't moved between mutex locks.
if (expectedIndex >= m_ids.size() || m_ids[expectedIndex] != nextId)
{
auto it = (nextId < m_nextId)
? std::lower_bound(m_ids.cbegin(), m_ids.cend(), nextId)
: m_ids.end();
if (it == m_ids.end())
{
return false;
}
expectedIndex = std::distance(m_ids.cbegin(), it);
}
slot.reset();
slot = m_functions[expectedIndex];
nextId = (expectedIndex + 1 < m_ids.size()) ? m_ids[expectedIndex + 1] : m_ids[expectedIndex] + 1;
++expectedIndex;
return true;
}
size_t signal_impl::count() const noexcept
{
std::lock_guard lock(m_mutex);
return m_functions.size();
}
} // namespace is::signals::detail
代码实现了一个高性能的信号(signal)机制的核心部分,属于信号槽(signal-slot)系统的基础设施。
核心数据结构
class signal_impl {
mutable spin_mutex m_mutex; // 自旋锁,保证线程安全
std::vector<packed_function> m_functions; // 存储槽函数的容器
std::vector<uint64_t> m_ids; // 槽函数ID容器
uint64_t m_nextId = 1; // 下一个可用的槽ID
};
关键方法解析
1 添加槽函数
uint64_t add(packed_function fn);
-
将槽函数打包成
packed_function
类型存入容器 -
返回唯一ID用于后续管理连接
2 移除槽函数
void remove(uint64_t id) noexcept;
void remove_all() noexcept;
-
通过ID精确移除特定槽函数
-
remove_all
清空所有槽函数
3 槽函数计数
size_t count() const noexcept;
-
返回当前连接的槽函数数量
核心触发机制
template <class Combiner, class Result, class Signature, class... Args>
Result invoke(Args... args) const
{
// 获取槽函数并调用的实现...
}
1 void返回值处理
if constexpr (std::is_same_v<Result, void>) {
while (get_next_slot(slot, slotIndex, slotId)) {
slot.get<Signature>()(std::forward<Args>(args)...);
}
}
-
遍历所有槽函数并调用
-
使用完美转发保持参数类型
2 非void返回值处理
else {
Combiner combiner;
while (get_next_slot(slot, slotIndex, slotId)) {
combiner(slot.get<Signature>()(std::forward<Args>(args)...));
}
return combiner.get_value();
}
-
使用组合器(Combiner)处理多个槽函数的返回值
-
典型组合器如
last_value
、optional_last_value
等
线程安全实现
mutable spin_mutex m_mutex;
-
使用自旋锁(
spin_mutex
)而非标准互斥锁 -
适合高频低竞争场景,减少线程切换开销
-
mutable
允许const方法修改锁状态
槽函数管理
bool get_next_slot(packed_function& slot, size_t& expectedIndex, uint64_t& nextId) const;
-
安全遍历槽函数的帮助方法
-
处理并发场景下的容器遍历问题
智能指针别名
using signal_impl_ptr = std::shared_ptr<signal_impl>;
using signal_impl_weak_ptr = std::weak_ptr<signal_impl>;
-
使用共享指针管理生命周期
-
弱指针用于避免循环引用
典型使用场景
// 定义信号
using SignalType = is::signals::signal<void(int)>;
// 连接槽函数
SignalType sig;
auto conn = sig.connect([](int x) { ... });
// 触发信号
sig(42); // 调用所有连接的槽函数
这段代码展示了一个工业级信号槽实现的核心机制,平衡了性能、灵活性和线程安全需求,适合在高性能场景下替代传统的观察者模式实现。