Serial Studio 组合根改造(Spec 0001):用显式构造顺序与依赖捕获终结 59 个 Meyers 单例的启动竞态
2026/9/17 12:18:20 网站建设 项目流程

Serial Studio 组合根改造(Spec 0001):用显式构造顺序与依赖捕获终结 59 个 Meyers 单例的启动竞态

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

本篇技术指南以doc/claude/specs/0001-composition-root/spec.md为骨架,结合同目录下的 plan.md、tasks.md 以及仓库当前源码,完整讲解 Serial Studio 将约 59 个 Meyers 单例服务收敛为"组合根 + 依赖捕获"架构的动机、目标、设计、验收标准与七阶段落地路径。读者将理解:为什么惰性单例的构造顺序会让启动图依赖用户保存的设置、如何用instantiateCoreModules()固定拓扑顺序并消灭AppState<->ProjectModel重入隐患,以及如何在不回归 256 kHz 热路径的前提下把 1,852 处散落的X::instance()调用点收缩到受监管的许可面内。

背景与问题:59 个 Meyers 单例的隐式构造顺序

Serial Studio 的约 59 个核心服务全部是 Meyers 单例——在第一次调用X::instance()时才惰性构造。问题在于:这个构造顺序不是由代码定义的,而是由"哪个instance()调用恰好最先触发"涌现出来的,而第一次触发取决于用户机器上保存的设置。

spec 文档给出了一个具体而典型的例子:在一台保存了operation_mode为 ProjectFile 的机器上,AppState的构造函数会执行deriveFrameConfig(),其 ProjectFile 分支调用ProjectModel::instance()——于是ProjectModelAppState构造函数内部被构造;而在 QuickPlot 机器上该分支从不执行,ProjectModel被推迟到更晚才构造。开发者在某台机器上验证过的启动图,只是众多顺序中的一种;任何重构若只在一种顺序下验证过,都是不完整的验证。

这个"设置条件性边界"(settings-conditional edge)不只是不整洁,而是一个活的重入风险

  • ProjectModel的构造函数调用newJsonFile(),后者会发出groupsChanged信号,而此时AppState仍处于构造中途;
  • 如果在newJsonFile()之前就连接了触碰 AppState 的 lambda,就会在 AppState 自己的 Meyers 初始化过程中重入AppState::instance()——这是未定义行为;
  • 维护者此前靠调整一次connect调用的顺序躲过了这个问题,源码注释(spec 中引用的ProjectModel.cpp:446:"newJsonFile() emits groupsChanged while AppState is still mid-init")正是这一险情的记录。

换言之,惰性依赖图今天之所以还像一个 DAG,只是靠一条注释和一个精心放置的 connect 碰巧维持的;任何一次 connect 重排都可能触发 UB,而没有任何机制把它钉在原地

与此同时,ModuleManager事实上已经是组合根(composition root)——它强制实例化各模块、按顺序执行 17 次setupExternalConnections()调用、随后restoreLastProject()、再注册约 60 个Cpp_*QML 上下文属性、最后加载 QML。缺少的只是:代码钉死的构造顺序,以及显式、被强制执行的"构造-接线"两阶段生命周期。Spec 0001 正是要把这一点形式化,让启动图不再依赖保存的设置,移除AppState<->ProjectModel隐患,并把松散散落的::instance()调用面(59 个单例头文件、1,852 个调用点)在回归守卫的监督下逐步收缩——同时不回归 256 kHz 热路径。

目标与非目标

目标(Goals)

  • 核心模块的构造顺序由代码定义,在 QuickPlot、ProjectFile、Console-only 三种启动模式下完全一致,绝不随持久化的operation_mode变化。
  • 消除AppState<->ProjectModel重入隐患:ProjectModel永远先于AppState构造,使设置条件性边界无法形成。
  • ModuleManager成为唯一、显式的组合根,具备两阶段生命周期(先构造全部模块,再接线);三条既有顺序不变式(INV-1..INV-3,见下文)由构造保证而非偶然成立。
  • 非热路径消费者把依赖作为捕获成员持有,而非到处散布X::instance()调用;松散调用点数量逐波严格下降。
  • 回归守卫使任何新增::instance()调用点在 code-verify 报告中可见,调用面只减不增。
  • 256 kHz 热路径门禁保持或改善(成员加载比 Meyers 守卫的原子加载加分支更廉价)。

非目标(Non-Goals)

  • 不是容器/框架式依赖注入(DI):约 60 个Cpp_*QML 上下文属性需要稳定、长寿命的QObject地址;容器会引发约 60 参数的接线爆炸和 1,852 个调用点的 flag-day 式重写;QML 从中得不到任何好处;且最终形态与普通构造函数注入在结构上完全相同——捕获成员将来可以机械地提升为构造参数。容器在这里买不到任何东西,却要付出一场重写的代价。
  • 不是服务定位器(service locator):它把同样的全局状态藏在更慢、更难 grep 的查找后面,在 256 kHz 不允许间接成本的热路径上增加开销,且对真正的病灶——未钉死的惰性构造顺序——毫无作用。
  • 不删除单例,也不改变 QML 上下文属性模型。instance()访问器和约 60 个上下文属性都保留;改变的只是依赖的获取方式
  • 不把FrameParser转换为捕获或强制实例化依赖(见附录特例):其构造闭包依赖项目内容,保持惰性/延迟。

目标架构:组合根 + 依赖捕获

Spec 0001 主张的是形式化的"组合根 + 依赖捕获",而非引入新机制。

组合根:ModuleManager拥有启动

ModuleManager拥有启动过程。新的私有方法instantiateCoreModules()最先执行,在一个由代码钉死的拓扑顺序中强制每个核心模块进入存在,使依赖图不再取决于哪个instance()先触发。该顺序的承载性质(load-bearing property)是ProjectModel先于AppState,这直接消灭设置条件性边界。

两阶段生命周期:先构造,再接线

  • 阶段一(构造)instantiateCoreModules()依次构造模块。每个构造函数只做自初始化、读取QSettings、连接到模块自身强制的对象。
  • 阶段二(接线):按顺序对每个模块执行setupExternalConnections(),连接跨模块的信号/槽。
  • 随后restoreLastProject()(INV-1)、注册上下文属性(INV-2)、安装 Qt 消息处理器(INV-3)、加载 QML。

这正是 DI 会强加的 construct/wire 拆分,只不过表达在我们已有的根里。

依赖捕获(Dependency Capture)

取代每个使用点的即席X::instance(),消费者一次性捕获依赖并持有:

  • 真叶类与近叶类:作为构造函数初始化列表中的引用成员(-Wreorder+ 零警告在构建期抓住初始化顺序错误);
  • 五边形(pentagon)核心模块AppStateProjectModelFrameBuilderConnectionManagerDashboard):这些类在接线前就可能被触达,因此在它们各自的setupExternalConnections()顶部以指针形式捕获,接线前的表面继续保持直接instance()调用;
  • 热路径最后转换,并受基准门禁约束。

Ratchet(棘轮收缩)

一条建议性(advisory)lint 规则标记新增的松散::instance()调用点;许可面逐阶段收缩,直至只剩组合根、main入口、类自身的instance()定义和setupExternalConnections函数体。

三条不变式:组合根必须保住的顺序

INV-1:接线先于项目恢复

每个模块的setupExternalConnections()必须运行在restoreLastProject()之前。恢复项目会驱动接线逻辑,其前提是所有模块都已连接完毕。源码印证:ModuleManager.cpp 中appState->restoreLastProject()位于全部setupExternalConnections()调用之后。

INV-2:上下文属性注册时序

约 60 个Cpp_*QML 上下文属性必须在所有接线完成之后、QML 引擎加载之前注册,保证 QML 永远不会绑定到半接线的模块。

INV-3:Qt 消息处理器的安装时机

qInstallMessageHandler(MessageHandler)只能在Console::HandlerNotificationCenter存在之后安装。原因很细:消息处理器在第一次警告时会从任意线程构造这两个类;而Console::Handler的构造函数会拉取CommonFonts,后者触碰字体数据库,必须运行在 GUI 线程。过早安装消息处理器,可能在第一条零散警告出现时导致非 GUI 线程访问字体数据库。

附加约束

  • 不得回归 256 kHz 的--benchmark-hotpath门禁(全部七档)。
  • 必须在 QuickPlot、ProjectFile、Console-only 三种模式下行为一致。
  • 不得给FrameReader/CircularBuffer添加互斥锁;热路径信号跳转保持Qt::DirectConnection;依赖捕获不得改变热路径上的连接类型。
  • 行为保持:固定顺序中的每个强制实例化模块,必须是组合根今天已经传递构造的模块(仅自初始化 +QSettings+ 自强制连接),不引入任何新构造,只是把构造提前钉住
  • 作用域纪律:在上下文属性注册时才首次构建的模块(ProjectEditorProtoImporterExamplesHelpCenter等)保持在原处,不拉入固定核心顺序。

需求与验收标准

编号需求验收标准(均已勾选完成)
R1核心模块构造顺序由代码定义,QuickPlot/ProjectFile/Console-only 一致,不随operation_mode变化AC1:三种模式均正常启动运行;组合根在每种模式下都先构造 ProjectModel 再构造 AppState
R2ProjectModel 永远先于 AppState 构造,deriveFrameConfig() -> ProjectModel::instance() -> newJsonFile() -> groupsChanged重入边无法再形成AC1(同上)
R3所有模块接线经 ModuleManager 两阶段完成,INV-1..INV-3 成立AC2:restoreLastProject()在所有setupExternalConnections()之后;上下文属性在接线后、QML 加载前注册;消息处理器在Console::Handler/NotificationCenter存在后安装
R4非热路径消费者以捕获成员持有依赖;松散::instance()调用点逐波严格下降、永不增加AC3:每波转换后grep -c "X::instance()"在构造函数初始化列表(叶类)或接线前表面(五边形)之外为零;头文件每个依赖恰好新增一个成员;advisory 计数严格下降
R5新建议性 lint 规则报告许可面外的新增::instance()调用点,不改变阻塞错误数AC4:规则落地前后python3 scripts/code-verify.py app/src的阻塞错误数完全一致,advisory 报告只新增该种类;合成Foo::instance()片段触发规则,各许可模式不触发
R6热路径捕获阶段后--benchmark-hotpath七档全部通过,无回归;逐帧依赖访问是成员加载而非 Meyers 守卫AC5:七档全部通过,与阶段前基线相比无回归

实现路径:七个门控阶段

plan.md 把改造拆成七个门控阶段(S1-S7),对应 tasks.md 中的任务 T1-T9,全部标记完成。

构建门控(贯穿所有实现阶段)

  • 一次构建一个阶段:每个构建-启动周期只落地一个阶段(或一个 S4 波次/一个五边形类),绝不把两个顺序敏感变更混进同一次构建。
  • 三种操作模式全部启动:任何改变构造顺序(S3)或模块接线(S5)的阶段之后,都要在 QuickPlot、ProjectFile、Console-only 三种模式下启动验证。
  • S6 之后跑--benchmark-hotpath:全部七档,与 S6 前基线对比;S3/S4/S5 在逐帧路径之外,不强制跑基准,但仍需启动冒烟测试。

S1:依赖普查 + 初始化顺序契约(文档)— 已完成

编写 spec/plan/tasks 包与 architecture.md 新增小节"Composition Root & Construction Order -- ModuleManager":包含验证过的构造器边图(spec 附录表)、S3 固定实例化顺序、三条不变式 INV-1..INV-3,以及逐文件::instance()清点(1,852 个调用点、59 个单例头文件)作为晨间波次的预划定。验证命令:python3 scripts/documentation-verify.py

S2:回归守卫arch-singleton-instance建议性规则(Python)— 已完成

在 scripts/code_verify_rules.py 中新增建议性规则,并把它注册进 scripts/code-verify.py 的_ADVISORY_KINDS。当前源码中的实现要点(见 code_verify_rules.py 的 "Composition-root rule (spec 0001)" 注释段):

  • 触发正则:\b[A-Za-z_][\w:]*::instance\(\)_SINGLETON_INSTANCE_RE);
  • Qt 自身的静态访问器(QCoreApplication::instance()等)先被_QT_INSTANCE_RE从行内剥除,因为它们不是 Serial Studio 单例、没有未钉死的构造顺序;
  • 许可面(不产生 finding):组合根文件、访问器自身instance()函数体、setupExternalConnections()接线函数体、过渡期static auto& x = X::instance()热路径缓存惯用法、构造初始化列表捕获、以及单行Q_ASSERT(...)表达式;
  • _SINGLETON_SANCTIONED_FUNCS = frozenset({"instance", "setupExternalConnections"})定义了许可函数集合。

验证:规则落地前后code-verify.py app/src阻塞错误数一致、advisory 报告只新增该种类;合成片段触发、各许可模式不触发。

S3:钉死构造顺序(形式化组合根)— 已完成

新增私有void instantiateCoreModules();,作为setupCrossModuleConnections()第一行调用。当前源码实现在 ModuleManager.cpp,其固定构造顺序(与 spec 设计一致,ProjectModelAppState之前是承载性质)为:

  1. Translator
  2. TimerEvents
  3. CommonFonts
  4. WorkspaceManager
  5. NotificationCenter
  6. ThemeManager
  7. ExtensionManager
  8. ControlScript
  9. ProjectModel(先于AppState——消灭设置条件性边)
  10. AppState
  11. LemonSqueezy/MachineID(商业版)
  12. FrameBuilder
  13. IO::ConnectionManager
  14. Console::Handler
  15. API::Server
  16. CSV::Player
  17. MDF4::Player
  18. Sessions::Player(商业版/可选)
  19. 四个导出器
  20. FrameParser
  21. UI::Dashboard(最后——其构造函数触碰五个核心模块 + 两个播放器 +TimerEvents

注意当前源码已演进:instantiateCoreModules()同时做了消息总线挂接(attachMessageBus)、通过SessionContext收养模块(adoptProjectModel/adoptAppState等)与许可状态发布(商业版),但ProjectModel先于AppState的顺序被严格保留。作用域规则:只列出setupCrossModuleConnections今天已传递构造的模块;上下文属性注册时才构建的模块(ProjectEditorProtoImporterExamplesHelpCenter等)留在原处。

验证(grep 对称性):instantiateCoreModules中的每个类也出现在后面的setupCrossModuleConnections中或已被传递构造;顺序与 spec 表完全一致;restoreLastProject()仍在所有setupExternalConnections()之后(INV-1)。

S4:叶类构造器引用捕获(宽泛、按波次)— 已完成

对每个消费者类,添加引用成员(如Misc::TimerEvents& m_timerEvents;),在构造初始化列表初始化(m_timerEvents(Misc::TimerEvents::instance())),并把文件内非热路径调用替换为成员。已验证零单例出边的叶类:TranslatorTimerEventsCommonFontsNotificationCenterWorkspaceManagerCSV::PlayerMDF4::PlayerCommandHandler(外加近叶ThemeManager)。-Wreorder+ 零警告在构建期抓住初始化顺序错误——初始化顺序错误是编译期错误而非运行时错误。

三个波次:(1)Misc/*+UI/Widgets/*中消费 CommonFonts/TimerEvents/ThemeManager 的类;(2)Console/CSV/MDF4/;(3)DataModel/编辑器。

逐文件验证:(a).cppgrep -c "X::instance()"在构造初始化列表之外为 0;(b) 头文件每个依赖恰好新增一个成员;(c) 初始化列表位置与成员声明顺序一致;(d) advisory 计数严格下降。

S5:五边形延迟指针捕获 — 已完成

AppStateProjectModelFrameBuilderConnectionManagerDashboard各添加X* m_x(构造初始化列表置 nullptr),在各自setupExternalConnections()顶部赋值,使用点Q_ASSERT(m_x);方法体调用点转为m_x->构造器体与接线前可达的方法保持直接instance()——这些"接线前表面"包括:AppState::deriveFrameConfig()ProjectModel::newJsonFile()/setModified()/watchProjectFile()及其可达路径、Dashboard构造函数连接的一切、ConnectionManagerm_uiDriverSaveTimerlambda。

顺序(每次晨间构建一个类):AppState->ConnectionManager->FrameBuilder->ProjectModel->Dashboard。S3 之后AppState可以构造器捕获ProjectModel(因为 S3 保证 ProjectModel 先构造),但绝不允许从 ProjectModel 构造器捕获 AppState(活体 A<->B 隐患)。

当前源码印证:AppState.cpp 中m_projectModel(DataModel::ProjectModel::instance())已出现在构造初始化列表,setupExternalConnections()(同文件第 122 行)捕获m_frameBuilder = &DataModel::FrameBuilder::instance()并连接jsonFileChanged等信号——五边形捕获已落地。

验证(每类):头文件恰好新增指针成员;每个被转换的调用点位于已被证明在接线后运行的方法中;接线前表面仍用直接instance();advisory 计数下降;三种模式启动。

S6:热路径捕获(基准门控,最后)— 已完成

ConnectionManager::{onFrameReady,onRawDataReceived,processPayload,processMultiSourcePayload}Dashboard::updateStreamAvailable中的static auto&缓存静态量转换为 S5 指针成员;把onFrameReady中逐帧的AppState::instance().operationMode()轮询替换为缓存的m_operationMode,由operationModeChanged信号刷新(复用 FrameBuilder 的缓存标志模式)。所有连接保持Qt::DirectConnection

风险点:若m_operationMode未接入operationModeChangedonFrameReady中的操作模式分支会静默变陈旧。验证:转换后的调用点是成员而非静态量;m_operationMode刷新已接到operationModeChanged--benchmark-hotpath全部七档无回归

S7:棘轮收缩 — 已完成

arch-singleton-instance的许可面收缩到{ModuleManager.cpp, main.cpp, 类自身 instance() 定义, setupExternalConnections 函数体},去掉 S6 已移除的static auto&缓存惯用法豁免;可选地把五边形提升为按类阻塞。此后新服务一律从构造参数获取依赖

源码印证:组合根当前的实际形态

仓库当前代码完整反映了 spec 的落地方案:

  • ModuleManager.cpp 的setupCrossModuleConnections()第一行即调用instantiateCoreModules(),随后执行bindInterfaces()registerApiHandlers()、各模块setupExternalConnections(),最后appState->restoreLastProject()(INV-1);
  • instantiateCoreModules()ProjectModelctx.adoptProjectModel(...))严格先于AppStatectx.adoptAppState(...))构造;
  • ModuleManager.hinstantiateCoreModules/bindInterfaces/registerApiHandlers/setupHeadlessSessionConnections均为公开静态方法,并被 CLI.cpp 的无界面路径(基准、会话校验、schema 导出)复用——固定顺序成为所有启动形态的公共底座;
  • AppState.cpp 展示了"叶式引用成员 + 接线期指针捕获"的混合形态;
  • scripts/code_verify_rules.py 中的arch-singleton-instance规则与 spec/plan 描述的触发正则以树-sitter 作用域实现,对 Qt 静态访问器做了显式剥离。

落地后的回归事故(tasks.md 事后记录)

tasks.md 记录了一次值得警惕的回归:落地次日凌晨发现,某个在newJsonFile()内加入 AppState/Dashboard 同步的修复在 ProjectModel 构造函数内执行,在 ProjectFile 机器上递归了 Meyers 守卫(__cxa_guard_acquire在启动时 abort)。T3 的构造器边证明在书写时是正确的,但后续修复波次使其失效。修复方式:用m_initialized标志门控该同步。由此确立一条常驻规则:任何修改 ProjectModel 构造闭包(newJsonFile、watchProjectFile、scheduleAutoSave、setCode 链)的编辑,在合并前必须重新触发构造器边检查

权衡与备选方案

plan.md 的权衡表可概括为:

决策点选项选择及理由
整体形态容器 DI / 服务定位器 / 组合根+捕获组合根+捕获——ModuleManager 本就是根;两阶段 construct/wire 是 DI 的拆分,却免去约 60 参数爆炸和 1,852 点 flag-day;热路径严格改善
ProjectModel vs AppState 顺序保持设置条件性 / 钉 PM 优先 / 钉 AppState 优先钉 ProjectModel 优先——正是生产环境 ProjectFile 机器每天运行的顺序,且消灭重入边
叶类捕获机制构造初始化列表引用 / 延迟指针 / 惰性访问器构造初始化列表引用——-Wreorder使初始化顺序错误成为编译期错误;仅在接线前可达的类用延迟指针
热路径时机随 S5 一起 / 延迟到门控的最后阶段延迟到 S6 基准门控——把唯一逐帧可见的变化隔离在--benchmark-hotpath之后
守卫强度立即阻塞 / 建议后棘轮建议后棘轮——1,852 个既有调用点使阻塞式 flag-day 不可行;建议制让调用面逐波收缩

风险与缓解

  • 构造期重入(核心隐患):S3 钉住 ProjectModel 先于 AppState,设置条件性边无法形成;ProjectModel.cpp:446的围栏表面与所有接线前表面保持直接instance()(绝不捕获)。
  • 叶引用成员初始化顺序错误-Wreorder+ 零警告使其成为构建失败。
  • 接线前使用未赋值的五边形指针:使用点Q_ASSERT(m_x);S5 明确接线前表面清单;一次构建一个类。
  • 陈旧缓存标志导致热路径静默故障:S6 把m_operationMode接入operationModeChanged--benchmark-hotpath门禁兜底。
  • 强制实例化导致行为变化:作用域规则只钉住根已传递构造的模块;S3 grep 对称性检查。
  • 1,852 个调用点上的范围蔓延:严格波次边界;advisory 计数逐波严格下降、永不增加。

测试与验证计划

  • 单元:无直接单元测试(无tests/scripts/JS 面);S2 lint 规则用合成Foo::instance()片段自测,并对比code-verify.py前后阻塞计数。
  • 静态python3 scripts/documentation-verify.py校验 S1 文档;每波对变更的 C++ 文件跑python3 scripts/code-verify.py --check;各阶段 grep 对称性配方;每个 C++ diff 经qt-cpp-review
  • 集成(维护者执行):S3 与每个 S5 五边形类之后在 QuickPlot、ProjectFile、Console-only 三种模式启动,确认正常启动、项目恢复与控制台输出。
  • 热路径(维护者执行):S6 后跑--benchmark-hotpath全部七档,与 S6 前基线对比;deploy.yml对发布的 PGO 二进制做最终兜底门禁。
  • 提交:每次提交前运行python3 scripts/sanitize-commit.py;工作树无 lint 债务。

附录:构造器捕获安全表(前 15 大单例)

下表来自 spec 附录("Capture BY others" = 其他类捕获该类是安全的;"Own deps capture mode" = 该类应如何获取自己的依赖):

#单例(调用点数)已验证构造器出边可被他人捕获自身依赖捕获模式
1ProjectModel (343)ControlScript(经 newJsonFile,PM.cpp:2116)可以,但 ControlScript/worker 除外setupExternalConnections中的延迟指针;ctor/newJsonFile 表面保持直接调用。绝不可 ctor 捕获 AppState(活体 A<->B 隐患)
2ConnectionManager (301)可以延迟指针(AppState、ProjectModel、FrameBuilder、Console::Handler、API::Server);可 ctor 捕获 Translator
3Dashboard (152)CSV::Player、MDF4::Player、ConnectionManager、AppState、FrameBuilder、Sessions::Player、TimerEvents(Dashboard.cpp:172-229)不可以——无人 ctor 捕获 Dashboard延迟指针;ctor 连接保持
4AppState (146)ProjectModel(条件性,deriveFrameConfig)可以,但 ProjectModel 与 ControlScript 除外S3 后可 ctor 捕获 ProjectModel;其余不可
5ThemeManager (58)WorkspaceManager(loadUserThemes)、Translator可以,但这两者除外两者均可 ctor 引用捕获
6FrameBuilder (56)LemonSqueezy -> MachineID(商业版)可以延迟指针;热路径成员仅在 S6
7Console::Handler (33)CommonFonts可以;必须早于qInstallMessageHandler存在可 ctor 捕获 CommonFonts
8CommonFonts (31)无(GUI 线程字体库)可以真叶类
9Translator (29)可以真叶类
10TimerEvents (28)可以真叶类
11API::Server (27)CommandHandler(平凡)可以可 ctor 捕获 CommandHandler
12CSV::Player (26)可以近叶(qApp 过滤器)
13WorkspaceManager (25)无(mkdir + 遗留迁移)可以真叶类
14MDF4::Player (25)可以近叶
15NotificationCenter (25)可以;必须早于消息处理器存在真叶类

附录:FrameParser 特例

FrameParser(23 个调用点)只能保持惰性/延迟:其构造函数调用engineForSource(0),而 Lua 引擎构造会触达FrameBuilder::instance()(spec 附录引用的 LuaScriptEngine.cpp:167);构造闭包依赖项目内容,因此FrameParser不能被 ctor 捕获。它仍可出现在固定顺序中被强制实例化(因为根已传递构造它),但任何类都不得把它作为成员捕获。


延伸阅读:完整规范见 spec.md,技术设计见 plan.md,任务清单与落地记录见 tasks.md;架构总览见 architecture.md;lint 规则实现见 code_verify_rules.py 与 code-verify.py;组合根实现在 ModuleManager.cpp。

【免费下载链接】Serial-StudioOpen-source telemetry dashboard. Supports UART, BLE, MQTT, Modbus, CAN Bus and more.项目地址: https://gitcode.com/GitHub_Trending/se/Serial-Studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询