C++策略模式实战:从接口到std::function与variant的四种变体
前几年我参与评审一个网关服务某个同事用一大串if-else处理不同设备的上报报文。代码能跑但每加一种新业务都得回头动那个百来行的大函数改完还得担心把旧逻辑碰坏。当时我建议用策略模式重构落地之后才发现一件事教科书里的策略模式只是一张最基础的地图C工程实践里真正常见的是它的好几种变体每种变体解决不同的问题代价也完全不同。这篇文章就把这些变体从头到尾梳理一遍从最经典的接口加实现类到std::function的轻量化写法再到模板参数驱动的编译期策略最后聊聊variant和注册表这两种面向真实业务的编排方式。无论你是在维护老项目还是在设计新模块应该都能找到对得上号的那一种。1. 先从教科书策略模式说起接口加实现类的基线版本1.1 三件套结构策略接口、具体策略、上下文对象策略模式最经典的形态是把“可变化的算法”抽成一个接口让上下文对象持有一个策略实例在运行时动态替换它。拿我当年那个压缩模块举例核心结构大概长这样// 策略接口 class ICompressor { public: virtual ~ICompressor() default; virtual std::vectorchar compress(const std::vectorchar input) 0; }; // 具体策略A class GzipCompressor : public ICompressor { public: std::vectorchar compress(const std::vectorchar input) override { // gzip 具体实现 return {}; } }; // 具体策略B class BrotliCompressor : public ICompressor { public: std::vectorchar compress(const std::vectorchar input) override { // brotli 具体实现 return {}; } }; // 上下文持有策略但不关心策略内部细节 class DataDumper { public: void setCompressor(std::unique_ptrICompressor compressor) { compressor_ std::move(compressor); } void dump(const std::vectorchar raw) { auto compressed compressor_-compress(raw); // 写文件、发送到网络等 } private: std::unique_ptrICompressor compressor_; };这段代码几乎是设计模式教材的原样复刻ICompressor定义算法契约GzipCompressor和BrotliCompressor提供具体实现DataDumper只依赖接口不依赖具体类。调用方负责把合适的策略塞进上下文。1.2 这个基线版本的两个核心价值我一直在用第一个价值是运行时替换策略。比如写了配置项compressor.type brotli程序启动时根据配置文件构造对应的策略对象不需要改动DataDumper一行代码。第二个价值是开闭原则新加一种压缩算法等于新增一个类主流程和已有策略都不用动。分布式系统里加一个新的序列化协议、新的鉴权方式都是这个套路。这两个价值在真实项目里非常实在尤其是当你面对一个长期演进、策略种类持续增长的模块时接口加实现类仍然是最稳的骨架它能清晰表达“这里是扩展点”后来的维护者一看就明白该往哪儿加代码。1.3 但经典方案也有几处让我越来越不舒服的地方第一策略类太多。如果策略本身只有三五行算法差异也要专门定义一个类样板代码多过业务代码。我见过一个日志格式化模块定义了十几个策略类每个类里就一个十来行的format函数结果一大半代码是在写类声明、构造函数和文件分发。第二虚函数调用带来间接跳转。现代 CPU 对间接调用有分支预测大多数情况下代价不高但在性能极敏感的循环里每次往复都要走一次虚表查找而且虚函数没法被内联。编译器能看到GzipCompressor::compress的全部实现却因为虚调度无法内联进调用点这对高频调用的策略是一个实实在在的隐性成本。第三策略之间如果共享一段公共逻辑继承体系会变得扭曲。为了复用要么把公共代码塞进基类导致基类越来越膨胀要么让策略类之间出现“兄弟继承”这种非常别扭的关系。这些压力才是后面几种变体出现的真实原因而不是为了炫技。2. 函数式策略变体std::function 加 lambda 的轻量化解法2.1 用函数签名替代策略接口如果策略本身没有内部状态也不需要一个完整的对象建模std::function是最直接的轻量化替代。它把“策略”这个概念从“类”降维成“可调用对象”。同样的日志格式化模块用函数式变体可以写成这样using FormatStrategy std::functionstd::string(const LogRecord); // 策略直接定义为 lambda连类都不用建 const FormatStrategy textFormatter [](const LogRecord rec) { return rec.level : rec.message; }; const FormatStrategy jsonFormatter [](const LogRecord rec) { return {\level\:\ rec.level \,\msg\:\ rec.message \}; }; class Logger { public: void setFormatter(FormatStrategy strategy) { formatter_ std::move(strategy); } void log(const LogRecord rec) { auto output formatter_(rec); // 写日志 } private: FormatStrategy formatter_; };这个形态下策略的数量跟类的数量完全解耦。加一种策略就是加一个 lambda 变量定义点和使用点往往靠得很近代码读起来更连贯。配合std::map或unordered_map还能实现“按字符串查策略”的注册表效果这部分后面第 4 节再展开。2.2 为什么这个变体往往是默认选择小策略场景里函数式变体比接口版本舒服得多。首先是没有继承层级不会出现“为了一个三行函数写一个类”的尴尬。其次是 lambda 能直接捕获上下文里的变量策略定义处就能把依赖一起收进来而接口版本必须通过构造函数传参。再者std::function类型本身可以放进容器、可以拷贝、可以赋值这给策略组合带来了很大便利。比如把多个格式化策略放到一个std::vectorFormatStrategy里按顺序执行接口版本要实现一个“策略列表”就麻烦得多。在业务代码里凡是策略只需要一个函数、不需要生命周期管理、不需要内部状态的场景我都会默认先写std::function真正需要对象语义时再升级成接口版本。2.3 轻量化的代价类型擦除与调试障碍std::function的核心机制是类型擦除它用统一类型包住任意可调用对象。代价之一是多一层间接调用函数调用的开销略高于直接调用。代价之二是捕获状态的 lambda 可能触发堆分配虽然大多数标准库实现有小型对象优化但捕获成员特别多、lambda 特别大的时候优化可能失效就会产生一次堆分配。调试上也有一个细节std::function在调试器里看到的往往是一个黑盒你很难直接看出里面装的是哪个 lambda。策略数量一多光靠断点判断“当前到底走的是哪个策略”会比接口版本费劲。我的实践体会是策略代码超过二十行或者策略需要维护自己的状态就老实回归接口版本策略只是个配置化的小选择std::function非常划算。这个度一定要自己拿捏没有放之四海而皆准的标准。3. 编译期策略变体模板参数驱动与零成本抽象3.1 让策略在编译期固定下来接口版本和std::function版本都是运行时多态策略对象在程序运行期间可以被替换。但有一种情况很特殊策略的选择范围在编译期就是确定的甚至就是写代码的人决定的那完全可以把策略做成模板参数让编译器在编译期完成绑定。// 模板参数指定策略类型 template typename Formatter class MessageWriter { public: void write(const Message msg) { std::string output Formatter::format(msg); // 写消息 } }; struct JsonFormatter { static std::string format(const Message msg) { return {\id\:\ msg.id \,\body\:\ msg.body \}; } }; struct BinaryFormatter { static std::string format(const Message msg) { // 二进制序列化 return {}; } }; // 使用策略在编译期绑定 MessageWriterJsonFormatter jsonWriter; jsonWriter.write(message);这个形态下Formatter::format是静态函数编译器能完全内联调用策略的开销几乎为零。MessageWriterJsonFormatter和MessageWriterBinaryFormatter是两个完全不同的类型不存在运行时切换策略也不存在虚表这是真正意义上的零成本抽象。3.2 带上 CRTP 之后策略可以“用到父类的能力”模板策略的另一个有趣变体是结合 CRTP奇异递归模板模式。策略类通过模板参数把自己的类型传给基类基类在编译期就知道子类类型从而在基类里定义依赖于子类实现的模板方法。这种形式适合策略需要一套公共逻辑但又想保持编译期多态的场景伪结构大概长这样template typename Derived class BasePolicy { public: std::string wrap(const std::string raw) { // 公共前置逻辑 auto processed static_castDerived*(this)-process(raw); // 公共后置逻辑 return processed ]; } }; class UpperPolicy : public BasePolicyUpperPolicy { public: std::string process(const std::string raw) { return raw [upper; } };这里BasePolicy::wrap在编译期就知道UpperPolicy的类型process调用实际上被内联成一个直接调用不需要虚函数。CRTP 策略变体在模板库内部比较常见比如给容器定制分配行为、给算法组件定制比较规则。普通业务代码里用得少一点因为对阅读者而言模板继承的理解成本明显高一个台阶。3.3 我踩过的模板策略的坑运行时变化时非常难受模板策略最大的问题也来自它的优势策略一旦绑定运行时想换就难了。我之前在一套埋点 SDK 里把上报格式做成了模板策略老实说编译期性能确实好但后来产品要求按用户配置在运行时切换格式我和同事面对模板代码改了好几天最后不得不引入一个std::function适配层把模板策略包起来绕了一大圈才搞定。第二个坑是模板实例化导致的代码膨胀。MessageWriterJsonFormatter和MessageWriterBinaryFormatter各自实例化一套完整代码如果策略种类多、模板的代码又长二进制体积会明显上涨。第三个坑是报错可读性差模板参数一旦不满足约束编译器吐出来的往往是几十行模板内部错误新手基本看不懂。因此模板策略适合“策略集合在编译期完全确定、混合扩展概率低、性能敏感”的场景。系统调用边界、中间层框架里的分配策略和序列化策略我会优先考虑模板业务逻辑层里面向运营配置的策略我基本不会用模板。4. 注册表策略和 variant 策略面向真实业务的编排变体4.1 用 map 构建策略注册表把选择权交给配置当策略数量比较多而且希望策略的选择完全由外部数据比如配置文件、数据库记录驱动时注册表模式是一个很实用的变体。它本质上是用一个std::unordered_mapstd::string, std::function...把策略名映射到策略实现class MessageParser { public: using ParseFn std::functionMessage(const std::vectorchar); void registerParser(const std::string name, ParseFn fn) { parsers_[name] std::move(fn); } Message parse(const std::string name, const std::vectorchar raw) { auto it parsers_.find(name); if (it parsers_.end()) { throw std::runtime_error(unknown parser: name); } return it-second(raw); } private: std::unordered_mapstd::string, ParseFn parsers_; }; // 初始化阶段注册策略 MessageParser parser; parser.registerParser(json, [](const std::vectorchar raw) { // json 解析 return Message{}; }); parser.registerParser(binary, [](const std::vectorchar raw) { // binary 解析 return Message{}; });注册表的优点非常直接新增策略的修改点被收敛到一个注册集中主流程完全不动。配合配置中心甚至可以做到不改代码、只改配置就切换报文解析方案。注意注册表的本质是std::function的容器化升级所以前面谈到的类型擦除开销在这里同样存在但换来的是极强的扩展性。4.2 用 variant 封装“已知全集”让编译器帮我把分支查完另一种业务场景是策略的全集在编译期就完全已知而且数量不多比如一个协议帧只可能是 XML、JSON、二进制之一。这种时候可以用std::variant替代继承体系把策略类型作为变体的备选项class XmlPolicy { public: void apply() const { /* xml 处理 */ } }; class JsonPolicy { public: void apply() const { /* json 处理 */ } }; class BinaryPolicy { public: void apply() const { /* binary 处理 */ } }; using Policy std::variantXmlPolicy, JsonPolicy, BinaryPolicy; void dispatch(const Policy policy) { std::visit([](const auto p) { p.apply(); }, policy); }std::visit本质上会生成一张以variant的 index 为索引的分发表效果上接近于手写的 switch编译器能确认所有分支都被覆盖。这比接口版本省掉了新建策略类的成本也比裸 switch 多了类型安全性漏掉一种策略直接编译不过。这个变体的适用条件很严格策略全集必须稳定且有限。如果今天猜三种、明天加第四种variant就会变成一个频繁修改的类型别名反向增加维护成本。我一般只在底层协议处理、编译器前端这类“全集闭锁”场景里使用它。4.3 策略链与多种变体的互相嵌套真实系统里这几种变体经常是混着用的。最常见的一种组合是外层注册表驱动的多格式导入管道内层每种格式再用variant穷举小粒度分支也有外层是模板策略的编译期管线内层某个环节的动态选择交给std::function。还有一种值得一提的管道式策略用std::vectorstd::functionData(const Data)表达一个策略链让数据依次经过多个策略处理比如先压缩再加签名再加密。策略链的好处是每一段策略职责单一整条链可以在配置里动态组合。这已经算是对策略模式的一种结构性扩展但它的思想基础仍然是“把变化抽成可替换的单元”。5. 策略模式误用实录我踩过和见过的几种坑5.1 为“未来可能的变化”提前抽象结果三年没变化有一次我参与设计一个支付对接模块当时团队讨论得热火朝天说将来要支持多种网关于是基于策略模式抽象了一套接口还配上了工厂和配置项。结果三年过去系统只接了一个支付网关那套策略接口和工厂代码成了纯摆设。删除的时候还得小心有没有人当“模板”去抄。这个教训我会一直记得策略模式只有在“已经有两个以上真实策略”或者“策略变化有明确的业务驱动”时才划算。为一个不存在的未来做抽象本质是给现在的代码加税。唯一的策略不要套策略模式先硬编码等第二个策略真实出现时再重构成本完全可控。5.2 策略模式和状态模式边界模糊改出二义性 bug另一个让我印象深刻的坑是有人把连接状态迁移也写成了策略模式。状态对象各自维护跳转逻辑结果策略里又能改上下文的状态逻辑越埋越深最后出现“同一个事件在两种状态下走了同一套策略”的诡异问题。策略模式和状态模式表面上都是“把行为按类型拆分”但本质差异很大。策略是被动的由外部调用方决定换哪个策略策略本身不改变上下文的核心身份状态是主动的状态对象负责根据事件迁移到下一个状态。把这两个概念混在一起代码会很快失去可读性。判断标准很简单如果一个“策略”会主动修改上下文的状态它更可能是状态模式不该用策略接口硬装。5.3 策略粒度太细把代码切得七零八落还有一类问题不是用错模式而是边界切得太碎。有人为了“纯粹”把一个流程的每一步都抽象成策略结果策略对象之间互相依赖调用链全靠装配代码串联换一个人看基本看不懂。合理的策略粒度应该跟业务变化对齐而不是跟代码行数对齐。如果某一处永远不会替换或者替换成本极低直接写死比抽象成策略更清晰。6. 选型参考与几个我一直在用的编码习惯6.1 四种变体横向对照变体绑定时机额外开销典型适用场景接口 实现类运行时一次虚函数间接调用策略会持续增长、需要独立文件维护、需要生命周期管理std::function运行时类型擦除 潜在堆分配小策略、配置化、回调组合、策略可作为值传递模板参数编译期几乎没有运行开销性能敏感、策略全集编译期已知、无需运行时切换variant运行期极小分发表跳转策略全集稳定且有限需要编译器穷举检查注册表是基于std::function的容器化升级适合策略特别多、需要由配置驱动的场景。它不改变单次策略调用的开销模型只是把“选哪个策略”挪到了更灵活的注册集里。6.2 代码组织建议策略目录、注册入口、单元测试实际工程里我习惯把不同类型的策略放到独立的目录文件名一眼能区分。每个策略一个头文件策略接口单独一个头文件工厂或注册入口单独一个文件。这样新策略落地时改动面最小评审也只盯策略注册处有没有把 key 和实现匹配对。策略对象本身尽量设计成无状态或状态可重建的这样测试成本最低。每个策略至少一个单元测试直接调用策略对象断言输出不依赖上下文主流程的测试再验证策略注入是否生效。把策略的测试和上下文的测试分开问题定位时能快很多。6.3 代码评审时我常问的三个问题收到一个策略模式相关变更时我一般会先问三个问题。第一策略的替换触发者是谁是代码动态决定的还是运行时配置还是编译期就定死了触发者决定了该选哪种变体。第二策略数量增长是真实需求还是想象出来的如果只有一种策略还抽象接口会直接拉高认知成本。第三最坏情况下策略调用频率有多高每秒上百万次循环里的策略和一天触发一次的策略选型标准完全不同。别一上来就套“经典策略模式”的模板先把这三个问题过一遍选择会自然浮现。最后想分享一点个人体会。设计模式书把策略模式讲得很简单但工程里真正难的从来不是“写出策略模式”而是“判断该用哪种变体、甚至该不该用”。接口版本、std::function、模板参数、variant这几种形态不是哪个高级哪个低级的关系分别对应不同维度的成本取舍。把它们都放进同一个工具箱动手的时候反而更从容也不用等着重构的那一天才意识到选错了。

相关新闻

基于SpringBoot的家教管理系统:设计与实现全拆解

基于SpringBoot的家教管理系统:设计与实现全拆解

1. 项目到底在解决什么问题——业务场景与功能拆解作为折腾过不少同类管理系统的开发者,我第一眼看到“基于SpringBoot的家教管理系统的设计与实现”这个标题,就大概猜到它是市面上很典型的一类Web开发题目。很多时候大家拿到这种题目,第一反…

2026/10/11 4:06:59 阅读更多 →
YOLO26算法城市道路行人目标检测+训练好的模型+12051张数据集+pyqt可视化界面

YOLO26算法城市道路行人目标检测+训练好的模型+12051张数据集+pyqt可视化界面

YOLO26算法城市道路行人目标检测训练好的模型12051张数据集pyqt可视化界面 这套数据共 12051 张图。用 YOLO26 训了 100 轮,mAP50 0.9608,mAP50-95 0.8159。Precision 0.9432、Recall 0.9042。下面按数据构成 → 训练曲线 → 预测效果的顺序过一遍&…

2026/10/11 4:05:58 阅读更多 →
Claude组团审代码:25美元一条PR的AI代码审查与安全边界

Claude组团审代码:25美元一条PR的AI代码审查与安全边界

先说一个结论:当你看到"Claude 上线组团审代码,一条 PR 最高 25 美元,你的代码库还得上交给它"这条消息时,不要把注意力全放在 25 美元和隐私焦虑上。更值得琢磨的是产品形态的变化——从"聊天框里问 AI 这段代码有…

2026/10/11 4:05:58 阅读更多 →

最新新闻

DSH 桌面客户端装插件:一次 --profile 选错的完整排查

DSH 桌面客户端装插件:一次 --profile 选错的完整排查

本文实测环境:Windows DeepSeek Harness 官方桌面客户端(Electron)。文中的路径统一用环境变量表示,示例属于脱敏写法;插件本身是公开仓库 MeteorNOX/DeepSeek-Balance-Whale-Widget。 起因 我想给 DSH 装一个"…

2026/10/11 6:27:17 阅读更多 →
Ubuntu 26.04 的 Windows Spotlight 最终方案:强烈推荐 Spotlight Desktop

Ubuntu 26.04 的 Windows Spotlight 最终方案:强烈推荐 Spotlight Desktop

在 Ubuntu 26.04 上,我一直很喜欢折腾桌面壁纸。 倒不是单纯想每天换一张图片,而是一直比较喜欢 Windows 11 的 Windows Spotlight(Windows 聚焦):每天自动换一张质量不错的风景壁纸,同时还能知道这张照片…

2026/10/11 6:27:17 阅读更多 →
国漫大模型推荐2026:Illustrious、NoobAI、Anything V5对比,ComfyUI 8G显存选型指南

国漫大模型推荐2026:Illustrious、NoobAI、Anything V5对比,ComfyUI 8G显存选型指南

你是不是已经能用 SD1.5 基础模型出图了,但每次画出来的“国漫”,总觉得像日漫穿了件汉服——形似神不似? 别慌,我一开始也这样,换了三个模型才找到感觉。 这篇不教操作,就做一件事:帮你在 Illustrious、NoobAI、Anything V5 三个国漫模型里,选出一个最适合你当前阶段的…

2026/10/11 6:27:17 阅读更多 →
Linux 日志增量统计:inode + offset 方案(不丢不重)

Linux 日志增量统计:inode + offset 方案(不丢不重)

背景 我给 Nginx 缓存命中率写了个统计脚本,每 5 分钟跑一次,读 /var/log/nginx/dashboard_cache.log,统计 HIT/MISS 数量写进 MariaDB。 第一版逻辑很简单: tail -n 100 /var/log/nginx/dashboard_cache.log | awk {...}跑了两天…

2026/10/11 6:27:17 阅读更多 →
Web 安全自学完整路线,从零搭建属于自己的渗透测试知识体系

Web 安全自学完整路线,从零搭建属于自己的渗透测试知识体系

Web 安全自学完整路线,从零搭建属于自己的渗透测试知识体系 摘要 很多想要入行网络安全、学习 Web 渗透测试的小伙伴,刚开始都会陷入迷茫:不知道先学什么,网上资料杂乱零散,教程东拼西凑,学了很久依旧不会实…

2026/10/11 6:27:17 阅读更多 →
Claude Code Subagent 状态栏监控原理与实战

Claude Code Subagent 状态栏监控原理与实战

1. 这不是普通状态栏插件:Claude Code 的 Subagent 状态为什么值得单独监控你有没有过这样的体验:在 Claude Code 里提交一个复杂任务,比如“重构整个 utils 模块并生成单元测试”,界面只显示一个模糊的“正在处理中”&#xff0c…

2026/10/11 6:26:17 阅读更多 →

日新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/11 0:00:27 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 5:23:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/9 21:32:20 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 10:38:42 阅读更多 →