C++ PIMPL 惯用法与编译防火墙深度解析
核心主张PIMPL 不是花活而是用一次编译时间换取长期 ABI 稳定与编译速度的生产级惯用法。1. 从一个抓狂的编译场景说起假设你维护一个共享库对外暴露了 Person 类// person.h —— 库的对公开头文件 #include string #include vector #include database_connection.h // 内部依赖 #include cache_manager.h // 内部依赖 class Person { public: Person(const std::string name); std::string getName() const; void setAddress(const std::string addr); private: std::string name_; std::string address_; DatabaseConnection db_; // ① 内部实现细节 CacheManager cache_; // ② 内部实现细节 std::vectorint scores_; };灾难来了改一行内部分所有人重编译。你往 Person 里加了一个 mutable std::mutex lock_于是所有 #include person.h 的文件可能上千个全部触发重编译——哪怕它们根本没用锁。ABI 脆弱得像玻璃。修改成员变量后sizeof(Person) 变了v1.0 编译的客户端链接 v1.1 的库直接崩溃——因为对象布局object layout对不上了。头文件泄漏内部依赖。database_connection.h 被拖进每个包含 person.h 的文件暴露了实现细节。这就是 PIMPL 要解决的问题。PIMPL Pointer to IMPLementation通俗类比你租房时只关心门牌号和房间功能不需要知道墙里水管怎么走的。PIMPL 把怎么实现藏到 .cpp 文件里头文件只暴露一个门牌号指针。2. PIMPL 核心思想指针 前向声明2.1 三步拆解步骤动作类比① 头文件中只声明class Person { ... private: struct Impl; Impl* pImpl; };对外说我有个管家在打理内部事务但不告诉你管家是谁② Impl 定义藏到 .cpp在 .cpp 中完整定义 struct Person::Impl { string name; ... };管家具体怎么干活只有自己知道③ 所有方法委托给 pImplstring Person::getName() { return pImpl-name; }你让管家取名字管家去翻档案柜2.2 第一次改造// person.h — 清爽的头文件 #include memory // 仅需 unique_ptr 的前置依赖 class Person { public: Person(const std::string name); ~Person(); // ⚠️ 析构函数必须在这里声明在 .cpp 定义原因见第7节 std::string getName() const; private: struct Impl; // 前向声明不暴露任何字段 std::unique_ptrImpl pImpl; // 现代 C 推荐 unique_ptr };// person.cpp — 所有脏东西都在这里 #include person.h #include string #include vector #include database_connection.h // 这些重型头文件不再污染调用方 #include cache_manager.h struct Person::Impl { std::string name; std::string address; DatabaseConnection db; CacheManager cache; std::vectorint scores; }; Person::Person(const std::string name) : pImpl(std::make_uniqueImpl()) // C14 起推荐用 make_unique { pImpl-name name; } Person::~Person() default; // ⚠️ 必须在 Impl 完整定义之后 std::string Person::getName() const { return pImpl-name; }核心变化person.h 从依赖 4 个重量级头文件变成只需要 memory。无论 Impl 怎么改只要公有接口不变调用方一行都不用重编译。3. 编译防火墙机制图文对比3.1 编译依赖链对比不使用 PIMPL 的依赖关系person.h ──include──▶ database_connection.h │ │ │ ▼ │ maybe 200 files ▼ client_a.cpp ──重编译──▶ client_a.o (800ms) client_b.cpp ──重编译──▶ client_b.o (600ms) client_c.cpp ──重编译──▶ client_c.o (1200ms) ... (全部 N 个文件)当 database_connection.h 改动一个内部枚举指标无 PIMPL有 PIMPL受影响 .o 文件数全部 N 个包含 person.h 的仅 person.o1 个全量重编译时间N500~15 分钟~3 秒增量编译时间~15 分钟因为头文件变了~3 秒对外暴露私有成员是否修改私有成员后 ABI可能破坏不受影响3.2 为什么不叫封装而叫编译防火墙封装encapsulation是语义层面的——private: 关键字阻止外部代码访问成员。但编译器仍需要看到完整定义来计算 sizeof 和内存布局。编译防火墙compilation firewall是物理层面的——头文件里根本不存在成员定义编译器只看到一个指针。类比封装 房门上锁别人不能进但还能看到门和墙的形状。编译防火墙 房门加整面墙拆掉别人连门在哪里都不知道。4. ABI 稳定性原理4.1 什么是 ABI 破坏ABIApplication Binary Interface是二进制层面的接口约定包括对象内存布局成员偏移量虚函数表布局名称修饰name mangling规则函数调用约定最典型的 ABI 破坏场景// v1.0 class Widget { int x_; // offset 0 int y_; // offset 4 int z_; // offset 8 }; // v1.1 — 中间插了一个字段 class Widget { int x_; // offset 0 int new_; // offset 4 ← 爆破点 int y_; // offset 8 ← v1.0 编译的代码以为 y_ 在 offset 4 int z_; // offset 12 };v1.0 编译的客户端执行 widget.y 100 会写入 offset 4——而现在 offset 4 是 new_。静默数据损坏。4.2 PIMPL 如何保证 ABI 稳定// v1.0 — 头文件 class Widget { struct Impl; std::unique_ptrImpl pImpl; // 始终 8 字节64位系统 public: void set(int v); };无论 Impl 内部怎么膨胀——从 3 个字段变成 30 个字段、从 24 字节变成 2KB——sizeof(Widget) 始终不变始终 sizeof(unique_ptr) 8 字节。因为头文件中 Widget 只含一个指针。// v1.1 — 头文件完全不变 // Impl 在 .cpp 中偷偷加了 5 个新字段对外部零影响 struct Widget::Impl { int x, y, z; std::string a, b, c, d, e; // 新增外部完全不知道 std::vectorint history; // 新增 std::mutex lock; // 新增 };场景无 PIMPL有 PIMPL增删私有成员ABI 破坏客户端须重编译ABI 不变改变私有成员类型ABI 破坏ABI 不变sizeof(Widget) 变化是否可热更新hot-patch库几乎不可能可行通俗类比PIMPL 像是寄信只写邮政信箱编号。邮局内部搬了 10 次、扩建了 20 次你的信封上始终是那个编号。无 PIMPL 则是寄信写详细地址——门牌变一个数字信就送丢了。5. 内存布局变化从栈到堆5.1 对象模型对比无 PIMPL 的对象布局所有数据在栈/容器内联 ┌──────────────────────────────────┐ │ Widget (sizeof N bytes) │ │ ┌──name_──┬──addr_──┬──db_──... │ ← 所有字段平铺 │ └─────────┴─────────┴───────────│ └──────────────────────────────────┘ 有 PIMPL 的对象布局仅指针在栈/容器内数据在堆 ┌──────────────┐ ┌──────────────────────────┐ │ Widget │ │ Impl (堆上) │ │ ┌────────┐ │ ────▶ │ ┌──name_──┬──addr_──... │ │ │ pImpl ─┼──┤ │ └─────────┴─────────────│ │ └────────┘ │ └──────────────────────────┘ │ (8 bytes) │ └──────────────┘维度无 PIMPL有 PIMPLsizeof(Widget)随成员变化固定 8 字节64位对象内存位置全部在栈/容器内联指针在栈数据在堆构造开销成员就地构造额外一次堆分配 (make_unique)访问开销直接偏移间接指针解引用缓存局部性更好连续内存稍差两次内存访问移动语义效率逐成员移动仅移动 8 字节指针极快5.2 移动语义的意外红利因为 Widget 只有一个 unique_ptr 成员移动构造 / 移动赋值极其廉价// 无 PIMPL 的移动逐字段移动字段越多越慢 Widget(Widget other) : name_(std::move(other.name_)) , addr_(std::move(other.addr_)) , db_(std::move(other.db_)) // 这可能是重的 , scores_(std::move(other.scores_)) {} // 有 PIMPL 的移动只移动一个指针 Widget(Widget other) default; // 移动 unique_ptr8 字节 // 无论 Impl 有多少字段移动成本恒定这就是用一次堆分配的固定代价换取移动和拷贝的极致轻量。6. 现代 C 实现完整可运行代码6.1 完整实现unique_ptr 移动语义 拷贝支持// widget.h #pragma once #include memory #include string class Widget { public: // 构造与析构 explicit Widget(const std::string name); ~Widget(); // ⚠️ 在 .cpp 定义 // 移动语义编译器自动生成已满足需求移动 unique_ptr 即可 Widget(Widget) noexcept default; Widget operator(Widget) noexcept default; // 拷贝语义 — 需手动实现因为 unique_ptr 不可拷贝 Widget(const Widget other); Widget operator(const Widget other); // 业务接口 std::string getName() const; void setName(const std::string name); void addScore(int score); double averageScore() const; private: struct Impl; std::unique_ptrImpl pImpl; };// widget.cpp #include widget.h #include vector #include numeric #include stdexcept struct Widget::Impl { std::string name; std::vectorint scores; }; Widget::Widget(const std::string name) : pImpl(std::make_uniqueImpl()) { pImpl-name name; } // ⚠️ 析构函数必须在 Impl 完整定义之后否则 unique_ptr 无法析构不完整类型 Widget::~Widget() default; // 拷贝构造深拷贝 Impl Widget::Widget(const Widget other) : pImpl(std::make_uniqueImpl(*other.pImpl)) // 利用 Impl 的默认拷贝 {} // 拷贝赋值copy-and-swap 惯用法 Widget Widget::operator(const Widget other) { if (this ! other) { pImpl std::make_uniqueImpl(*other.pImpl); } return *this; } std::string Widget::getName() const { return pImpl-name; } void Widget::setName(const std::string name) { pImpl-name name; } void Widget::addScore(int score) { pImpl-scores.push_back(score); } double Widget::averageScore() const { if (pImpl-scores.empty()) { return 0.0; // 或抛异常视业务需求 } double sum std::accumulate(pImpl-scores.begin(), pImpl-scores.end(), 0.0); return sum / pImpl-scores.size(); }// main.cpp — 使用示例 #include widget.h #include iostream #include vector int main() { // 构造 Widget w(Alice); w.addScore(85); w.addScore(92); w.addScore(78); // 拷贝 Widget w2 w; // 深拷贝w2 拥有独立的 Impl w2.setName(Bob); // 移动 Widget w3 std::move(w); // w 被移空w3 接管 Impl std::cout w2: w2.getName() , avg w2.averageScore() std::endl; // 输出: w2: Bob, avg85.0 // 容器中高效存储仅 8 字节/元素移动代价极低 std::vectorWidget widgets; widgets.reserve(100); for (int i 0; i 100; i) { widgets.emplace_back(Widget_ std::to_string(i)); // 每次 emplace_back构造 Impl堆分配→ 移动 Widget仅移指针 } return 0; }6.2 CMakeLists.txtcmake_minimum_required(VERSION 3.14) project(PIMPL_Demo LANGUAGES CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_library(widget_lib STATIC widget.cpp ) target_include_directories(widget_lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) add_executable(demo main.cpp) target_link_libraries(demo PRIVATE widget_lib)6.3 编译并运行mkdir build cd build cmake .. cmake --build . ./demo # 或 demo.exe (Windows)7. 常见陷阱与最佳实践7.1 ⚠️ 陷阱一析构函数的定义位置这是 PIMPL unique_ptr 的踩坑率 #1// ❌ 错误在头文件中声明析构 default // widget.h class Widget { public: ~Widget() default; // 编译器在此展开析构尝试销毁 unique_ptrImpl private: // 但此时 Impl 不完整→ 编译错误 struct Impl; std::unique_ptrImpl pImpl; };原因unique_ptr 的析构函数会在 ~Widget() 中调用 delete pImpl编译器需要知道 Impl 的完整定义来计算析构逻辑。头文件中 Impl 仅前向声明不完整。✅ 正确做法// widget.h class Widget { public: ~Widget(); // 只声明不定义 }; // widget.cppImpl 完整定义之后 Widget::~Widget() default; // 此时 Impl 完整编译器可正确生成析构代码7.2 ⚠️ 陷阱二拷贝语义unique_ptr 不能拷贝所以编译器不会自动生成拷贝构造 / 赋值。你需要手动实现见 6.1 节代码。如果你不希望 Widget 被拷贝Widget(const Widget) delete; Widget operator(const Widget) delete;7.3 ⚠️ 陷阱三const 成员函数中的 pImplstd::string Widget::getName() const { return pImpl-name; // ✅ 正确访问的是 Impl 对象内部字段 // 解引用 const unique_ptrImpl 得到 Impl* const指针本身不可变 // 但指向的 Impl 对象是可变的逻辑 const 而非 bitwise const }这不会出问题——C 的底层 const 对 unique_ptr 只约束指针本身值不变不约束所指向的对象。7.4 最佳实践速查事项推荐做法智能指针选择优先 unique_ptr只有需要共享所有权时才用 shared_ptr析构函数在 .h 中声明在 .cpp 中定义 default移动语义直接 default移动 unique_ptr 即移动全部拷贝语义手动实现深拷贝或用 delete 禁用构造 Impl用 std::make_uniqueImpl()C14异常安全容器中使用优先 emplace_back 预留 reserve减少扩容时的拷贝文件命名widget.h / widget.cpp 对外可见widget_p.h / widget_p.cpp 可选存放 Impl 细节8. 是否应该使用 PIMPL决策框架PIMPL 不是万能药。下面用对比表格帮你在实际场景下做决策8.1 PIMPL 的代价代价影响每次构造 → 一次堆分配比栈上构造慢通常几十 ns → 几百 ns每次成员访问 → 指针解引用pImpl-xxx 多一次间接寻址代码量增加每个公开方法都要写转发代码调试稍不便调试器中看到的是 pImpl-xxx 而非直接字段无法内联编译器无法跨 .cpp 边界内联 PIMPL 方法8.2 决策对比表场景是否用 PIMPL理由对外发布的 SDK / 共享库✅ 强烈推荐ABI 稳定性是刚需大型项目 (10万行) 的核心头文件✅ 推荐编译时间收益显著频繁修改内部实现但接口稳定的类✅ 推荐避免全量重编译需要隐藏第三方库依赖避免调用方安装✅ 推荐头文件不需要 #include 第三方内部工具类 / 单文件使用❌ 不推荐收益抵不上复杂度性能热点路径上的高频小对象❌ 不推荐堆分配 间接访问开销不可忽略仅含 int/double 等标量的值类型❌ 不推荐没必要为几个字段引入堆分配模板类头文件必须全可见⚠️ 需变通参见 8.3 节8.3 模板类的 PIMPL 变通// 模板 PIMPL 的组合用类型擦除或显式实例化 // 方案一显式实例化仅支持已知类型 template typename T class MyTemplate { struct Impl; std::unique_ptrImpl pImpl; public: explicit MyTemplate(T val); // ... }; // 在 .cpp 中显式实例化 template class MyTemplateint; template class MyTemplatestd::string; // 其他类型不可用 // 方案二放弃 PIMPL接受模板头文件必须暴露实现通常可接受9. 常见问题速查表问题解答Q1PIMPL 和 Bridge 模式有什么区别PIMPL 是 Bridge 模式的一种实现特化。Bridge 更通用抽象/实现各自独立演化PIMPL 专指一个类隐藏实现细节以减少编译依赖。可理解为 PIMPL ⊆ Bridge。Q2为什么析构函数必须在 .cpp 中定义unique_ptrImpl 默认删除器需要调用 delete而 delete 不完整类型是未定义行为。在 .cpp 中定义析构函数时 Impl 已完整。如果用 shared_ptr 则无此限制删除器类型擦除但不推荐。Q3能用 shared_ptr 替代 unique_ptr 吗技术上可以且 shared_ptr 对不完整类型的析构更宽容。但 unique_ptr 更轻量无引用计数开销、语义更清晰独占所有权优先使用。Q4PIMPL 对象能在栈上使用吗可以。Widget w(name) 在栈上创建pImpl 指针在栈上所指向的 Impl 对象在堆上。这是最常见用法。Q5PIMPL 会影响运行时性能吗两次间接影响① 构造时一次堆分配~几百 ns② 成员访问时一次指针解引用。对绝大多数业务代码可忽略。对纳秒级热路径应实测。Q6为什么我的 default 移动构造报错检查析构函数是否是用户定义的包括 default 在 .cpp 中。用户定义析构 → 编译器不自动生成移动构造 → 需手动 default。Q7PIMPL 能搭配虚函数吗可以。虚函数在 Widget 中声明内部委托 pImpl-virtual_method()。但注意虚析构函数同样要在 .cpp 中定义。Q8能减少堆分配开销吗可以用 std::aligned_storage placement new 实现快速 PIMPLfast PIMPL将 Impl 内联在 Widget 内部。但会牺牲 ABI 稳定性——sizeof(Widget) 又会变化。权衡在于你更需要编译防火墙还是性能。10. 总结PIMPL 是一种用可控的运行时代价换取明确的工程收益的设计惯用法┌──────────────────────────────────────────┐ │ PIMPL 价值等式 │ ├──────────────────────────────────────────┤ │ │ │ 一次堆分配~200ns │ │ 一次指针解引用~1ns │ │ 少许模板代码 │ │ │ │ VS │ │ │ │ 编译时间15min → 3s500 文件项目 │ │ ABI 稳定性库可热升级 │ │ 头文件清爽零内部依赖泄漏 │ │ │ └──────────────────────────────────────────┘三条核心原则记住就能用对析构函数在 .h 声明在 .cpp 定义——这是 PIMPL unique_ptr 的命门。移动 白送 default拷贝需手动深拷贝——unique_ptr 的特性决定了这个分工。库对外接口用它内部热路径慎用——时机决定价值。掌握 PIMPL你就拥有了一把在编译速度和 ABI 稳定性之间游刃有余的钥匙。在大规模 C 项目和 SDK 开发中它是必备武器。

相关新闻

新《生产建设项目水土保持方案技术审查要点》要求下水土保持方案编制技术应用

新《生产建设项目水土保持方案技术审查要点》要求下水土保持方案编制技术应用

2020年12月7日,水利部水土保持监测中心发布新的水保方案技术审查要点,(水保监〔2020〕63号)。替换之前2014年9月发布的(水保监〔2014〕58号)。新审查要点依据水土保持法律法规和《生产建设项目水土保持技术…

2026/10/9 4:37:10 阅读更多 →
Cocos Creator 3D材质系统深度解析:Material与SharedMaterial实战指南

Cocos Creator 3D材质系统深度解析:Material与SharedMaterial实战指南

1. 项目概述:从“材质”到“共享材质”的认知跃迁 在Cocos Creator 3D(以下简称Cocos 3D)里做渲染效果,Material(材质)是你绕不开的核心概念。它定义了物体表面的视觉属性,比如颜色、光泽度、纹…

2026/10/14 13:47:21 阅读更多 →
网页富文本编辑器实现Excel公式粘贴的技术方案

网页富文本编辑器实现Excel公式粘贴的技术方案

1. 网页富文本编辑器处理Excel公式的技术挑战在办公自动化场景中,Excel公式的跨平台迁移一直是个棘手问题。当用户尝试将包含复杂公式的Excel表格粘贴到网页富文本编辑器时,通常会遇到三种典型情况:公式完全丢失,仅保留计算结果公…

2026/10/6 11:11:36 阅读更多 →

最新新闻

5G室分小区服务降质告警排查:光链路故障定位与处理

5G室分小区服务降质告警排查:光链路故障定位与处理

摘要:本文针对5G室分小区频繁上报服务降质告警的问题,结合网管提取的光链路信息展开排查。通过分析链路降速与光衰两大异常,逐一排除光模块不匹配、光纤链路异常等可能原因,最终定位故障源于远端光模块,经RRU侧处理后光…

2026/10/14 19:42:51 阅读更多 →
Flask的create_app 函数

Flask的create_app 函数

工厂函数 1. 为什么要有 create_app 这个函数? 在传统的单文件写法中,我们会直接写 app Flask(__name__)。但在企业级项目中,这有一个致命问题:配置(Config)和 扩展(Extensions)必须…

2026/10/14 19:42:51 阅读更多 →
开源ERP二次开发:中小企业低成本打造可定制系统的务实路径

开源ERP二次开发:中小企业低成本打造可定制系统的务实路径

开发ERP软件这件事,我从几个角度都碰过:自己从零写过、用低代码平台搭过、也在开源系统基础上做过二次开发。最近有个做零配件加工的朋友来问我,公司想上ERP,到底该走哪条路?这问题我特别有发言权。我的结论可能会让一…

2026/10/14 19:42:51 阅读更多 →
java学习日记-多态与好玩的写法

java学习日记-多态与好玩的写法

Payable[] methods {new Alipay(1000),new WechatPay(500),new CreditCard(2000)};学习java的时候,发现一段好玩的代码。 这个和平时写的Payable p new Alipay(),有所不同。 需要拆成三步理解: 声明一个数组:Payable[] methods …

2026/10/14 19:42:51 阅读更多 →
Akamai 宣布与Anthropic达成 116 亿美元多年期协议,以支持其日益增长的需求

Akamai 宣布与Anthropic达成 116 亿美元多年期协议,以支持其日益增长的需求

此前,Akamai 在本年度已表明,云基础设施服务(CIS)客户群累计达成的多年期承诺金额超过28 亿美元。从这些协议可以看出,市场对 Akamai 助力企业客户大规模构建、部署和运营 AI 工作负载的需求正在持续走高。作为 Akamai…

2026/10/14 19:42:51 阅读更多 →
iphone prores视频在windows用命令行一行转mp4

iphone prores视频在windows用命令行一行转mp4

背景情况和需求 iphone打开了prores录制了巨大的130g的一个4k原视频, 我不需要保留这么大的视频又希望能保留其视频内容, 我希望能压缩然后只需要正常视频即可不用这么详细的原视频. 限制条件: iphone存储爆满了, 没什么空间给我原地转换, 或用专业软件如剪映打开并转换; 我的电…

2026/10/14 19:41:51 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 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/13 22:01:27 阅读更多 →
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/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练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/14 7:53:34 阅读更多 →