ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FreeCAD 中的 FastSignals:基于 C++17 的高性能信号槽库及其从 Boost.Signals2 的迁移实践

FreeCAD 中的 FastSignals:基于 C++17 的高性能信号槽库及其从 Boost.Signals2 的迁移实践 FreeCAD 中的 FastSignals基于 C17 的高性能信号槽库及其从 Boost.Signals2 的迁移实践【免费下载链接】FreeCADOfficial source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler.项目地址: https://gitcode.com/GitHub_Trending/fr/FreeCAD导读FastSignals 是 FreeCAD 项目内置src/3rdParty/FastSignals的一套纯 C17 实现的信号与槽signals and slots库其公开 API 与 Boost.Signals2 兼容可作为后者的即插即用替代品。本文将围绕该库的设计动机、核心 API、弱引用绑定weak this用法以及从 Boost.Signals2 大规模迁移的完整步骤展开并结合 FreeCAD 源码如 src/App/Application.h 中大量fastsignals::signal成员展示真实应用形态。读完后你将掌握如何在 FreeCAD 中直接使用该库并能独立完成存量代码的平滑迁移。一、FastSignals 是什么FastSignals 是一个与 Boost.Signals2 API 兼容的 C17 信号/槽实现由 iSpring 团队开源并以 MIT 协议发布见 LICENSE。正如 README.md 所述它拥有三项核心承诺作为 Boost.Signals2 的 drop-in 替代品使用相同的 API迁移成本低更优的性能与更紧凑的二进制代码这是其存在的根本理由大部分操作线程安全包括并发的连接connect、断开disconnect与发射emit。它由紧凑的纯 C17 代码实现库结构集中在 libfastsignals 目录下头文件位于libfastsignals/include/fastsignals/实现文件位于libfastsignals/src/。FreeCAD 在 3rdParty 构建体系中通过 src/3rdParty/CMakeLists.txt 中的add_subdirectory(FastSignals)引入该库并在 App 核心层大量使用以 src/App/Application.h 为例文档对象的生命周期事件新建、删除、重命名、保存、撤销/重做、属性变更等均由fastsignals::signal承载例如signalNewDocument、signalDeletedObject、signalBeforeChangeObject、signalChangedObject。这说明 FastSignals 并非孤立的三方代码而是 FreeCAD 文档核心事件机制的实际基石。二、为什么选择 FastSignalswhy-fastsignals.md 明确列出了该库的取舍。优势比 Boost.Signals2 更快二进制代码更紧凑与 Boost.Signals2 相同的 API。局限与差异仅支持 C17 兼容编译器Visual Studio 2017、现代 Clang、现代 GCC缺少少量 Boost.Signals2 中极少使用的特性无法通过signal::connect_extended在槽内获得 connection不支持slot::track的关联对象跟踪——请改用bind_weak详见 bind_weak.md没有shared_connection_block类的临时信号阻塞功能注意源码层面实际提供了shared_connection_block见下文分析由于没有disconnect(slot)重载无法按槽等价断开任何其他 API 差异都被视为 bug欢迎上报。基准测试结果文档指出tests/libfastsignals_bench目录包含一个简单基准测试对比 Boost.Signals2 与 libfastsignals 在信号被高频发射且分别挂载 0、1、8 个活跃连接时的性能三种场景下 libfastsignals 快 36 倍。原文档给出了如下示例结果相对耗时数值越大代表相对 Boost 越快*** Results: measure emit_boost emit_fastsignals emit_boost/0 1.00 3.00 emit_boost/1 1.00 5.76 emit_boost/8 1.00 3.70 ***需要说明的是当前仓库中未包含该基准测试目录上述为原文档记录的数据如需复现应结合自身编译环境实测。源码层面的性能原理从实现看FastSignals 的性能优势有其结构基础signal_impl.h 使用std::vector存储槽函数与 ID配合自旋锁spin_mutex保护并发访问signal.h 的operator()直接通过invoke...遍历槽并调用而invoke内部使用if constexpr在编译期区分 void 与非 void 返回路径见 signal_impl.h非 void 时才构造 combiner 收集结果。这种“不为不使用的东西付费”的设计正是文档所强调的唯一的例外是——你始终要为多线程支持付费每次调用都涉及自旋锁。三、快速上手基础示例simple-examples.md 提供了两个可直接编译运行的最小示例。3.1 signal 与 connection// Creates signal and connects 1 slot, calls 2 times, disconnects, calls again. // Outputs: // 13 // 17 #include libfastsignals/signal.h using namespace fastsignals; int main() { signalvoid(int) valueChanged; connection conn; conn valueChanged.connect([](int value) { cout value endl; }); valueChanged(13); valueChanged(17); conn.disconnect(); valueChanged(42); }要点connect返回connection对象调用disconnect()后槽不再被调用因此最后一次valueChanged(42)无任何输出。3.2 scoped_connection 的 RAII 断开// Creates signal and connects 1 slot, calls 2 times, calls again after scoped_connection destroyed. // - note: scoped_connection closes connection in destructor // Outputs: // 13 // 17 #include libfastsignals/signal.h using namespace fastsignals; int main() { signalvoid(int) valueChanged; { scoped_connection conn; conn valueChanged.connect([](int value) { cout value endl; }); valueChanged(13); valueChanged(17); } valueChanged(42); }与connection不同scoped_connection在析构时自动断开连接RAII 语义因此离开作用域后valueChanged(42)同样不会触发任何槽。3.3 源码中的连接类型体系connection.h 完整定义了连接相关的类层次connection管理信号-槽连接生命周期connected()/disconnect()操作线程安全可在其他线程发射期间断开该类本身不线程安全不能跨线程共享同一连接对象advanced_connection支持临时阻塞回调执行内部持有advanced_connection_impl其m_blockCounter为std::atomicintshared_connection_block用于阻塞 advanced connection 的回调执行scoped_connection/advanced_scoped_connection析构时自动断开可移动不可拷贝scoped_connection提供release()释放连接。注意一个细节why-fastsignals.md声称 FastSignals 没有shared_connection_block但从当前仓库源码看connection.h 已提供该类的完整实现且 signal.h 提供connect(slot, advanced_tag)重载来配合阻塞功能仅限返回void的槽。因此迁移时若用到临时阻塞可参考该实现。四、核心 API 深入signal 类模板signal.h 是库的门面值得逐项理解template class Signature, template class T class Combiner optional_last_value class signal;Signature形如Return(Arguments...)的函数签名例如void(int)、void(const Document, bool)对应 FreeCAD 中signalNewDocument的声明方式Combiner结果合并器默认optional_last_value。其行为见 combiners.h非 void 时把所有槽返回值缩减为“最后一个槽的返回值”以std::optionalT呈现void特化则直接以void为result_type。关键成员成员说明connection connect(slot_type slot)订阅槽到信号返回连接对象每次调用信号作为函数对象时所有槽被依次调用advanced_connection connect(slot_type slot, advanced_tag)带阻塞能力的连接static_assert限定仅用于返回void的槽void disconnect_all_slots()断开所有槽std::size_t num_slots()返回已挂载槽数量bool empty()无任何槽时返回 trueresult_type operator()(args...)触发信号发射调用所有槽operator slot_type()允许信号自身作为另一信号的槽信号链此外signal禁止拷贝构造/拷贝赋值但支持移动std::swap通过 ADL 找到的自由函数swap实现signal.h。内部实现上signal持有std::shared_ptrdetail::signal_impl因此移动语义安全且拷贝被有意禁止。五、bind_weak弱引用绑定的正确姿势bind_weak.md 是 FastSignals 替代 Boost.Signals2track()/track_foreign()的官方方案。5.1 基本用法使用fastsignals::bind_weak替代std::bind确保绑定对象已销毁时方法不会被调用第一个参数为指向 T 类成员函数的指针第二个参数为shared_ptrT或weak_ptrT示例bind_weak(Document::save(), document, std::placeholders::_1)其中document为weak_ptrDocument或shared_ptrDocument。5.2 Weak this 惯用法bind_weak实现了经典的 “weak this” 惯用法用于避免异步/多线程程序中的悬垂指针与非法内存访问。原始手写形式如下struct Entity : std::enable_shared_from_thisEntity { int value 42; void print() { std::cout print called, num value std::endl; } std::functionvoid() print_later() { // ! weak this idiom here ! auto weak_this weak_from_this(); return [weak_this] { if (auto shared_this weak_this.lock()) { shared_this-print(); } }; } }; int main() { auto entity std::make_sharedEntity(); auto print entity-print_later(); // Prints OK. print(); // Prints nothing - last shared_ptr to the Entity destroyed, so weak_this.lock() will return nullptr. entity nullptr; print(); }5.3 用 bind_weak 规避接收者生命周期问题将Entity::print()连接到信号后在Entity销毁前后各发射一次销毁后bind_weak无法lock()到对象槽不会被调用也不会发生内存访问违例——第二次event()静默无事。#include fastsignals/signal.h #include fastsignals/bind_weak.h #include iostream using VoidSignal fastsignals::signalvoid(); using VoidSlot VoidSignal::slot_type; struct Entity : std::enable_shared_from_thisEntity { int value 42; VoidSlot get_print_slot() { // Here fastsignals::bind_weak() used instead of std::bind. return fastsignals::bind_weak(Entity::print, weak_from_this()); } void print() { std::cout print called, num value std::endl; } }; int main() { VoidSignal event; auto entity std::make_sharedEntity(); event.connect(entity-get_print_slot()); // Here slot called - it prints slot called, num 42 event(); entity nullptr; // Here nothing happens - no exception, no slot call. event(); }5.4 底层实现bind_weak.h 的实现以detail::weak_binder为核心它保存成员函数指针与std::weak_ptrClassType重载operator()时先m_pObject.lock()成功才调用成员函数失败则返回ReturnType()的默认构造值。对外提供四个重载覆盖 const/非 const 成员函数 ×shared_ptr/weak_ptr四种组合最终通过std::bind包装返回可调用对象。六、从 Boost.Signals2 迁移的完整指南migration-from-boost-signals2.md 面向“大型代码库从 Boost.Signals2 迁移到 FastSignals”的场景给出了处理已知迁移问题的正确路径。迁移中你大概率会遇到两类情况代码直接使用boost::signals2::命名空间与boost/signals2.hpp头文件代码间接依赖了boost/signals2.hpp隐式包含的第三方头文件。迁移动机回顾FastSignals不是 header-only二进制代码更紧凑使用 C17 可变参数模板、constexpr if等现代元编程技术编译更快、二进制更紧凑对多数代码库可能更快——不为不使用的东西付费多线程支持除外。Step 1创建别名头文件建议新建一个统一头文件把命名空间与类型别名映射到 FastSignals后续代码只需改动 include 路径即可。例如// signals.h #pragma once #include fastsignals/signal.h #include fastsignals/bind_weak.h namespace boost { namespace signals2 { // 将 Boost 命名空间映射到 fastsignals // using fastsignals::signal; ... } }Step 2重建并修复编译错误2.1 补全缺失的 includeBoost.Signals2 是 header-only 库会隐式包含大量 STL/Boost 头FastSignals 不会。例如#include boost/signals2.hpp // Also includes std::map, boost::variant, boost::optional, etc. // Compiled OK even without #include map! std::map CreateMyMap();迁移后这类代码必须显式包含对应头文件。下表给出了常见的缺失符号与应补的头文件ClassHeaderstd::map#include mapboost::variant#include boost/variant/variant.hppboost::optional#include boost/optional/optional.hppboost::scoped_ptr#include boost/scoped_ptr.hppboost::noncopyable#include boost/noncopyable.hppboost::bind#include boost/bind.hppboost::function#include boost/function.hpp如果只是想快速编译通过可以在你的signals.h头中一次性补上这些 include但官方明确警告不推荐在 FastSignals 中引入这些额外头文件见原文档代码注释。2.2 移除 void 信号槽中的多余 returnBoost.Signals2 允许如下代码无警告但 FastSignals 要求signalvoid(...)的槽不能返回非 void 值boost::signals2::signalvoid() event; event.connect([] { return true; });修复方式直接删除槽中的return或用 lambda 包装槽并忽略其返回值。2.3 用 bind_weak 替换 track() / track_foreign()Boost.Signals2 可通过track(...)/track_foreign(...)跟踪关联对象的生命周期。原文档给出了典型迁移对照左侧是 Boost 写法右侧是 FastSignals 写法。Boost.Signals2 写法#include boost/signals2.hpp #include iostream #include memory using VoidSignal boost::signals2::signalvoid(); using VoidSlot VoidSignal::slot_type; struct Entity : std::enable_shared_from_thisEntity { int value 42; VoidSlot get_print_slot() { // Here track() tracks object itself. return VoidSlot(std::bind(Entity::print, this)).track_foreign(shared_from_this()); } void print() { std::cout print called, num value std::endl; } }; int main() { VoidSignal event; auto entity std::make_sharedEntity(); event.connect(entity-get_print_slot()); // Here slot called - it prints print called, num 42 event(); entity nullptr; // This call does nothing. event(); }FastSignals 写法#include fastsignals/bind_weak.h #include iostream using VoidSignal fastsignals::signalvoid(); using VoidSlot VoidSignal::slot_type; struct Entity : std::enable_shared_from_thisEntity { int value 42; VoidSlot get_print_slot() { // Here fastsignals::bind_weak() used instead of std::bind. return fastsignals::bind_weak(Entity::print, weak_from_this()); } void print() { std::cout print called, num value std::endl; } }; int main() { VoidSignal event; auto entity std::make_sharedEntity(); event.connect(entity-get_print_slot()); // Here slot called - it prints slot called, num 42 event(); entity nullptr; // Here nothing happens - no exception, no slot call. event(); }两者行为一致对象销毁后再次发射信号均“无任何事发生”。迁移时把std::bind(Class::method, this)替换为fastsignals::bind_weak(Class::method, weak_from_this())即可。2.4 结果合并器Result Combiners的差异原文档在此处仅给出章节标题。结合 combiners.h 可推断FastSignals 默认且主要提供optional_last_value合并器——收集所有槽的返回值但只保留最后一个并以std::optional包装void 特化直接返回 void。这与 Boost.Signals2 的optional_last_value语义一致如果你的代码依赖其他 combiner如first_value、last_value等迁移时需要自行实现对应模板或调整逻辑。Step 3运行测试完成代码修改后运行你现有的全部自动化测试单元、集成、系统、压力、基准、UI 测试。按文档说法大概率不会报错若发现问题请向 FastSignals 项目上报 issue。七、测试与构建FastSignals 自带完整的测试体系见 tests 目录单元测试libfastsignals_unit_tests覆盖signal_tests.cpp、Function_tests.cpp、bind_weak_tests.cpp使用的测试框架是仓库内置的 Catch2catch.hpp压力测试libfastsignals_stress_testssignal_stress_tests.cpp用于验证并发场景下的线程安全行为与文档“并发 connect/disconnect/emit 线程安全”的声明相互印证。CMake 层面FastSignals/CMakeLists.txt 定义库目标cmake/functions.cmake 提供辅助构建函数。在 FreeCAD 中它经由 src/3rdParty/CMakeLists.txt 被加入构建业务代码只需#include fastsignals/signal.h即可使用如 src/App/Application.h 所示。八、FreeCAD 中的实战形态FreeCAD 在 App 核心层集中使用 FastSignals 实现文档与对象的事件总线。以 src/App/Application.h 为例fastsignals::signalvoid (const Document, bool) signalNewDocument; fastsignals::signalvoid (const Document) signalDeleteDocument; fastsignals::signalvoid () signalDeletedDocument; fastsignals::signalvoid (const Document) signalRelabelDocument; fastsignals::signalvoid (const Document) signalRenameDocument; fastsignals::signalvoid (const Document) signalActiveDocument; fastsignals::signalvoid (const Document) signalSaveDocument; fastsignals::signalvoid (const Document, const std::string) signalStartSaveDocument; fastsignals::signalvoid (const Document) signalUndoDocument; fastsignals::signalvoid () signalUndo; fastsignals::signalvoid (const Document) signalRedoDocument; fastsignals::signalvoid () signalRedo; fastsignals::signalvoid (const Document, const App::Property) signalBeforeChangeDocument; fastsignals::signalvoid (const Document, const App::Property) signalChangedDocument; fastsignals::signalvoid (const App::DocumentObject) signalNewObject; fastsignals::signalvoid (const App::DocumentObject) signalDeletedObject; fastsignals::signalvoid (const App::DocumentObject, const App::Property) signalBeforeChangeObject; fastsignals::signalvoid (const App::DocumentObject, const App::Property) signalChangedObject;这些信号覆盖了文档从创建、打开、保存、撤销/重做到对象与属性变更的完整生命周期供 Gui 层及其他模块订阅。此外src/App/Application.cpp 中DocOpenGuard以fastsignals::signalvoid ()引用信号展示了在 guard 类中按引用持有信号并用于 RAII 通知的典型模式。这种用法与本文第三节的基础示例完全同构signalvoid (Args...)声明 →connect订阅 → 通过operator()发射。理解 FastSignals 的核心 API就等于理解了 FreeCAD 核心层事件机制的钥匙。九、小结FastSignals 以纯 C17、紧凑实现、Boost.Signals2 兼容 API 和 36 倍的发射性能提升按原文档基准数据成为 FreeCAD 事件系统的底层信号库。迁移路径清晰创建别名头 → 重建修错补 include、清理 void 槽 return、bind_weak替换track→ 全量回归测试。若你的 C17 项目正受 Boost.Signals2 性能与二进制体积困扰直接在本仓库的 src/3rdParty/FastSignals 中阅读源码、测试与 docs 文档即可快速落地。【免费下载链接】FreeCADOfficial source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler.项目地址: https://gitcode.com/GitHub_Trending/fr/FreeCAD创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表