C++20约束限制TFunction为指定签名函数 转换TS订阅代码
C++20 实现TS泛型订阅器方案
核心约束实现(对应TS extends Function 泛型限制)
C++20 可以通过 concept 机制在编译期限制模板参数类型,完全替代老版本SFINAE的繁琐写法:
- 通用可调用约束:匹配所有普通函数、函数指针、lambda、重载
operator()的自定义函数对象,对应TS里TFunction extends Function的规则 - 指定签名约束:精确限制可调用对象的返回值、参数列表类型,不符合的类型直接编译报错
两个常用约束的实现如下:
#include <concepts> // 任意可调用类型约束 template <typename T> concept AnyCallable = requires(T& t) { &T::operator(); } || std::is_function_v<std::remove_pointer_t<T>> || std::is_member_function_pointer_v<T>; // 指定签名约束,用法:CallableSig<T, void(int, std::string)> 代表T必须是接收int、string返回void的可调用类型 template <typename T, typename Sig> concept CallableSig = std::is_convertible_v<T, std::function<Sig>>;
完整代码实现
对应TS接口和类的逻辑,注意C++没有GC,需要手动处理闭包的生命周期安全,避免悬垂指针:
#include <vector> #include <functional> #include <memory> #include <algorithm> #include <cstdint> // 前置声明 template <AnyCallable TFunction> class Subscriptions; template <AnyCallable TFunction> struct Subscription { // 对应TS readonly subscriber,加const禁止外部修改 const TFunction subscriber; // 存储unsubscribe闭包 std::function<void()> unsubscribe; // 自增唯一ID,解决可调用对象相等判断的兼容问题 uint64_t id = 0; }; // 默认模板参数对齐TS默认值:任意可调用类型 template <AnyCallable TFunction = std::function<void()>> class Subscriptions : public std::enable_shared_from_this<Subscriptions<TFunction>> { private: std::vector<Subscription<TFunction>> _subscriptions; uint64_t _id_counter = 0; // 构造函数私有,强制通过工厂方法创建,保证shared_from_this合法 Subscriptions() = default; public: static std::shared_ptr<Subscriptions> create() { return std::shared_ptr<Subscriptions>(new Subscriptions()); } Subscription<TFunction> subscribe(TFunction f) { auto weak_this = this->weak_from_this(); uint64_t new_id = ++_id_counter; Subscription<TFunction> sub{ .subscriber = std::move(f), .unsubscribe = [weak_this, new_id]() { if (auto self = weak_this.lock()) { // 按ID删除更稳妥,兼容所有可调用类型,和原TS逻辑等价 std::erase_if(self->_subscriptions, [new_id](const Subscription<TFunction>& s) { return s.id == new_id; }); } }, .id = new_id }; _subscriptions.push_back(sub); return sub; } // 辅助接口:获取当前订阅总数 size_t count() const { return _subscriptions.size(); } };
使用示例
#include <iostream> int main() { // 1. 默认任意可调用类型场景 auto subs = Subscriptions<>::create(); auto sub1 = subs->subscribe([]() { std::cout << "trigger sub1\n"; }); auto sub2 = subs->subscribe([]() { std::cout << "trigger sub2\n"; }); std::cout << "current sub count: " << subs->count() << "\n"; // 输出2 sub1.unsubscribe(); std::cout << "after unsubscribe count: " << subs->count() << "\n"; // 输出1 // 2. 指定函数签名场景:限制为接收int返回void的处理函数 using HandlerSig = void(int); auto int_subs = Subscriptions<std::function<HandlerSig>>::create(); auto int_sub = int_subs->subscribe([](int val) { std::cout << "get value: " << val << "\n"; }); int_sub.subscriber(456); // 输出get value: 456 int_sub.unsubscribe(); std::cout << "int subs count after unsub: " << int_subs->count() << "\n"; // 输出0 }
关键说明
- 约束生效逻辑:如果给Subscriptions传入不可调用类型(比如
int、std::string),编译器会直接抛出约束不满足的错误,不需要运行时检查,和TS泛型约束的行为完全对齐 - lambda闭包处理:unsubscribe逻辑直接用C++lambda实现,通过
std::weak_ptr捕获所属Subscriptions对象的弱引用,调用时先判断对象是否存活,从根源避免野指针崩溃,比TS的GC管理更安全可控 - 删除逻辑优化:原TS通过
filter生成新数组实现删除,这里用std::erase_if直接在原容器上操作,性能更高;同时改用唯一ID匹配订阅项,解决了有捕获lambda无法直接比较相等的问题,逻辑比原TS实现更严谨 - 签名约束扩展:如果需要强制模板参数必须匹配指定签名,只需要把模板参数声明从
AnyCallable TFunction换成CallableSig<TFunction, 你需要的签名>即可,比如template <CallableSig<void(int)> TFunction>就会限制所有传入的函数必须是接收int返回void的类型
内容的提问来源于stack exchange,提问作者kalamar
相关产品推荐
相关产品推荐

