【链接】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 sig;
auto conn = sig.connect([](int x) { std::cout << x; });
sig(42); // 输出"42"
struct MyClass {
void method(int x) { /*...*/ }
};
auto obj = std::make_shared();
is::signals::signal sig;
auto conn = sig.connect(is::signals::bind_weak(&MyClass::method, obj));
is::signals::signal sig;
auto adv_conn = sig.connect([](){ /*...*/ }, is::signals::advanced_tag{});
is::signals::shared_connection_block blocker(adv_conn);
sig(); // 阻塞时槽函数不会被调用
is::signals::signal sig1, sig2;
sig1.connect(sig2); // 连接信号到信号
sig1(); // 同时会调用sig2
该库提供了一个健壮的、线程安全的信号和槽实现,特别关注内存安全和性能。设计上既支持简单用法也支持高级用法,同时保持高效性。
#pragma once
#include "combiners.h"
#include "connection.h"
#include "function.h"
#include "signal_impl.h"
#include "type_traits.h"
#include
#if defined(_MSC_VER)
# include "msvc_autolink.h"
#endif
namespace is::signals
{
template 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 Combiner>
class signal : private not_directly_callable
{
public:
using signature_type = Return(signal_arg_t...);
using slot_type = function;
using combiner_type = Combiner;
using result_type = typename combiner_type::result_type;
signal()
: m_slots(std::make_shared())
{
}
/// 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, "Advanced connect can only be used with slots returning void (implementation limitation)");
auto conn_impl = std::make_shared();
slot_type slot_impl = [this, slot, weak_conn_impl = std::weak_ptr(conn_impl)](signal_arg_t... 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... args) const
{
return detail::signal_impl_ptr(m_slots)->invoke...>(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... args) {
if (auto slots = weakSlots.lock())
{
return slots->invoke...>(args...);
}
};
}
private:
detail::signal_impl_ptr m_slots;
};
} // namespace is::signals
namespace std
{
// free swap function, findable by ADL
template class Combiner>
void swap(
::is::signals::signal& sig1,
::is::signals::signal& sig2)
{
sig1.swap(sig2);
}
} // namespace std
signal
类实现了一个线程安全的信号/槽系统,是观察者模式的高效实现。
template class Combiner = optional_last_value>
class signal;
这是信号类的主模板声明,使用两个模板参数:
Signature
: 函数签名,表示信号和槽的类型
Combiner
: 组合器类型,默认为optional_last_value
信号类实现
template class Combiner>
class signal : private not_directly_callable
这是信号类的特化实现,继承自not_directly_callable
(防止直接调用)。
using signature_type = Return(signal_arg_t...);
using slot_type = function;
using combiner_type = Combiner;
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... args) const
{
return detail::signal_impl_ptr(m_slots)->invoke...>(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... args) {
// 实现略...
};
}
允许将信号作为槽函数使用,实现信号链式连接。
//这是一个类型别名定义,使用 using 关键字创建了一个名为CameraDataReady 的信号类型
using CameraDataReady = is::signals::signal&)>;
// 定义信号实例
CameraDataReady GrabDoneSignal;
// 连接槽函数
GrabDoneSignal.connect([](const auto& vecImages) {
std::cout << "Received " << vecImages.size() << " images\n";
for (const auto& result : vecImages) {
// 处理每个结果...
}
});
// 触发信号
std::vector vecImages= getVecImages();
GrabDoneSignal(defectResults);
///signal_impl.h
#include "function_detail.h"
#include "spin_mutex.h"
#include
#include
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
Result invoke(Args... args) const
{
packed_function slot;
size_t slotIndex = 0;
uint64_t slotId = 1;
if constexpr (std::is_same_v)
{
while (get_next_slot(slot, slotIndex, slotId))
{
slot.get()(std::forward(args)...);
}
}
else
{
Combiner combiner;
while (get_next_slot(slot, slotIndex, slotId))
{
combiner(slot.get()(std::forward(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 m_functions;
std::vector m_ids;
uint64_t m_nextId = 1;
};
using signal_impl_ptr = std::shared_ptr;
using signal_impl_weak_ptr = std::weak_ptr;
} // namespace is::signals::detail
cpp文件
#include "../include/signal_impl.h"
#include
#include
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 m_functions; // 存储槽函数的容器
std::vector m_ids; // 槽函数ID容器
uint64_t m_nextId = 1; // 下一个可用的槽ID
};
uint64_t add(packed_function fn);
将槽函数打包成packed_function
类型存入容器
返回唯一ID用于后续管理连接
void remove(uint64_t id) noexcept;
void remove_all() noexcept;
通过ID精确移除特定槽函数
remove_all
清空所有槽函数
size_t count() const noexcept;
返回当前连接的槽函数数量
template
Result invoke(Args... args) const
{
// 获取槽函数并调用的实现...
}
if constexpr (std::is_same_v) {
while (get_next_slot(slot, slotIndex, slotId)) {
slot.get()(std::forward(args)...);
}
}
遍历所有槽函数并调用
使用完美转发保持参数类型
else {
Combiner combiner;
while (get_next_slot(slot, slotIndex, slotId)) {
combiner(slot.get()(std::forward(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;
using signal_impl_weak_ptr = std::weak_ptr;
使用共享指针管理生命周期
弱指针用于避免循环引用
// 定义信号
using SignalType = is::signals::signal;
// 连接槽函数
SignalType sig;
auto conn = sig.connect([](int x) { ... });
// 触发信号
sig(42); // 调用所有连接的槽函数
这段代码展示了一个工业级信号槽实现的核心机制,平衡了性能、灵活性和线程安全需求,适合在高性能场景下替代传统的观察者模式实现。