You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

127 lines
3.0 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#pragma once
#include <functional>
#include <vector>
#include <unordered_map>
#include <atomic>
#include <mutex>
#include <memory>
#include "ScopedConnection.h"
/**
* Signal 类,用于事件通知
* 线程安全,支持 RAII 连接管理
*/
template <typename... Args>
class Signal
{
public:
using Listener = std::function<void(Args...)>;
using Token = size_t;
Signal() : m_impl(std::make_shared<Impl>()) {}
// 允许移动
Signal(Signal&&) = default;
Signal& operator=(Signal&&) = default;
// 禁止拷贝(因为底层是 shared_ptr拷贝会导致两个 Signal 共享同一个监听器列表,通常不符合预期)
Signal(const Signal&) = delete;
Signal& operator=(const Signal&) = delete;
/**
* @brief 添加监听器
*/
Token Connect(Listener listener)
{
std::lock_guard lock(m_impl->mutex);
Token token = ++m_impl->tokenCounter;
m_impl->listeners.emplace(token, std::move(listener));
return token;
}
/**
* @brief 移除指定令牌的监听器
*/
bool Disconnect(Token token)
{
std::lock_guard lock(m_impl->mutex);
return m_impl->listeners.erase(token) > 0; // 修正了拼写和返回值
}
/**
* @brief RAII 版本的连接函数
*/
[[nodiscard]]
ScopedConnection ConnectScoped(Listener listener)
{
Token token = Connect(std::move(listener));
// 使用 weak_ptr 打破循环引用,并防止野指针访问
std::weak_ptr<Impl> weakImpl = m_impl;
return ScopedConnection([weakImpl, token]()
{
// 尝试锁定 weak_ptr如果 Signal 已经被销毁lock 会返回空指针
if (auto impl = weakImpl.lock())
{
impl->Remove(token);
}
});
}
/**
* @brief 通知所有监听器
*/
void Emit(Args... args)
{
std::vector<Listener> listenersSnapshot;
{
std::lock_guard lock(m_impl->mutex);
if (m_impl->listeners.empty())
{
return;
}
// 创建快照:将回调拷贝出来,在锁外执行
// 避免死锁(回调中调用 Connect/Disconnect和迭代器失效
listenersSnapshot.reserve(m_impl->listeners.size());
for (const auto& [token, listener] : m_impl->listeners)
{
listenersSnapshot.push_back(listener);
}
}
for (const auto& listener : listenersSnapshot)
{
listener(args...);
}
}
/**
* @brief 清空所有监听器
*/
void Clear()
{
std::lock_guard lock(m_impl->mutex);
m_impl->listeners.clear();
}
private:
// 内部实现类,由 shared_ptr 管理生命周期
struct Impl
{
std::mutex mutex;
std::unordered_map<Token, Listener> listeners;
std::atomic_size_t tokenCounter{ 0 };
void Remove(Token token)
{
std::lock_guard lock(mutex);
listeners.erase(token);
}
};
std::shared_ptr<Impl> m_impl;
};