C++开发:FastSignals信号槽库

【链接】https://github.com/ispringtech/FastSignals

FastSignals 是一个 C++ 信号/槽(Signal/Slot)库,提供了线程安全的事件处理能力。类似于 Boost.Signals2 或 Qt 的信号槽机制,但专注于 高性能低开销。它适用于需要高效事件处理的场景,比如游戏引擎、高频交易系统或嵌入式开发。

核心组件

  1. 信号类 (signal.h)

    • 实现了一对多事件通知的观察者模式

    • 支持void和非void返回类型

    • 使用组合器模式处理多个槽函数的返回值

    • 提供连接管理和线程安全操作

  2. 连接管理 (connection.h)

    • connection: 基本连接句柄

    • advanced_connection: 支持阻塞回调执行

    • scoped_connection: 销毁时自动断开连接

    • shared_connection_block: 临时阻塞槽函数执行

  3. 函数包装器 (function.h, function_detail.h)

    • 高效替代std::function,带有小缓冲区优化

    • 以最小开销支持各种可调用类型

    • 提供类型安全的调用

  4. 线程安全

    • spin_mutex.h: 轻量级同步原语

    • 线程安全的断开连接操作

    • 跨线程的安全信号调用

关键特性

  1. 内存安全

    • 弱指针绑定(bind_weak.h)防止悬空引用

    • 自动清理已断开的槽函数

    • 小缓冲区优化实现高效槽函数存储

  2. 灵活的调用

    • 支持const和非const成员函数

    • 适当处理引用或值参数

    • 可自定义非void槽函数的结果组合

  3. 性能优化

    • 低竞争场景下的自旋锁

    • 最小化模板膨胀

    • 高效的槽函数存储和调用

  4. 高级功能

    • 可阻塞的连接

    • 信号到信号的连接

    • 作用域连接管理

使用示例

基本信号/槽连接

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_valueoptional_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);  // 调用所有连接的槽函数

这段代码展示了一个工业级信号槽实现的核心机制,平衡了性能、灵活性和线程安全需求,适合在高性能场景下替代传统的观察者模式实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值