跳转至内容
  • A place to talk about whatever you want

    22 主题
    168 帖子
    没有新主题
  • 7 主题
    20 帖子
    SPeakS

    @dustchens 链表结构损坏, 不闭环了 (如果问题解决可以把帖子状态设置为已解决

  • 开源软件 | 开源社区 | 开源理念 | 开源与商业 | 开源可持续发展 等相关话的交流讨论
    注: 这里的"开源"是泛化的共建共享概念, 范围包含 OSI的范围、自由软件、CC等相关内容

    57 主题
    246 帖子
    MoYingJiM

    补一个 0BSD,这个许可证很多时候也被放在与 Unlicense 和 WTFPL 相提并论的(都是公共领域)

  • 63 主题
    258 帖子
    SPeakS

    从去年 gcc 15.1 初步支持 import std 开始, 我就在 C++的模块化上做了很多实践

    前段时间, 突然产生了以 C++23 模块化为基础, 实现一个现代 C++模块化项目的构建工具。并以其能用自己从源码构建出自己也就是所谓的自举来做为初步目标, 目前这个目标已经实现

    欢迎对现代 C++和模块化感兴趣的朋友来用, mcpp 构建工具使用模块化特性 玩一玩或开发开发小项目 也欢迎大家来反馈和交流

    开源仓库: https://github.com/mcpp-community/mcpp

    mcpp

    一个 现代 C++ 模块化构建工具 — 纯 C++23 模块编写,已实现自举

    核心特性 C++23 模块原生支持 — import std 自动处理,文件级增量构建,模块依赖自动分析,零手动配置 纯模块化自举 — mcpp 自身由 43+ 个 C++23 模块组成,用自己构建自己,模块系统经实战验证 开箱即用 — 一条命令安装,内置 GCC 16 / LLVM 20 工具链,自动下载到隔离沙盒,不污染系统 集成依赖管理 — SemVer 约束解析、锁文件、跨项目 BMI 缓存、自定义包索引 多包工作空间 — Workspace 统一锁文件与版本管理,适合大型项目 为什么选择 mcpp ?

    mcpp 专门为 C++23 模块化开发 打造。如果你想在项目中使用 import std、模块接口单元(.cppm)、模块分区等现代 C++ 特性,mcpp 在 Linux 上能为你提供便捷且友好的开发体验:

    默认模块化 — mcpp new 创建的项目模板直接使用 C++23 模块,import std 开箱即用 文件级增量构建 — 基于 P1689 dyndep 的三层优化(前端脏检查 + 逐文件扫描 + BMI restat ),只重编真正变化的模块 一键创建 & 构建 — mcpp new hello && cd hello && mcpp build,工具链自动安装,无需手动配置编译器和构建系统 模块化生态 — mcpplibs 提供一系列可直接 import 的 C++ 模块化库,支持自定义包索引

    注: 早期版本 — mcpp 仍在积极开发中,接口和行为可能在后续版本调整。
    欢迎对现代 C++ 模块化构建工具感兴趣的开发者参与贡献。
    问题 / 反馈 / 想法欢迎在 issues 留言。

    快速开始 安装

    方式一:使用 xlings 安装(推荐)

    xlings install mcpp -y

    方式二:一键安装脚本

    curl -fsSL https://github.com/mcpp-community/mcpp/releases/latest/download/install.sh | bash

    安装到 ~/.mcpp/,自动加进 shell PATH 。删除 ~/.mcpp 即可干净卸载。

    方式三:让 AI 助手帮你安装

    将以下提示词复制给你的 AI 编码助手( Claude Code / Cursor / Copilot 等):

    阅读 https://github.com/mcpp-community/mcpp 的 README , 帮我安装 mcpp 并创建一个 C++23 模块项目,构建并运行。 项目的 .agents/skills/mcpp-usage/SKILL.md 有详细的使用指南。 创建项目 & 构建运行 mcpp new hello cd hello mcpp build mcpp run

    注:首次构建会初始化环境并获取工具链,可能需要一些时间。

    项目结构 hello/ ├── mcpp.toml ← 工程描述 └── src/ └── main.cpp ← import std; 直接可用 # mcpp.toml [package] name = "hello" [targets.hello] kind = "bin" main = "src/main.cpp" 使用模块化库

    在 mcpp.toml 中添加两行依赖,即可引用 mcpplibs 社区模块化库:

    [dependencies] cmdline = "0.0.2"

    然后在代码中直接 import:

    import mcpplibs.cmdline;

    更多依赖配置方式(版本约束、命名空间、Git 引用、本地路径等)参见 mcpp.toml 指南 — 依赖管理。

    平台支持 OS / arch GCC (glibc) GCC (musl) Clang / LLVM MSVC Linux x86_64 ✅ ✅ 默认 ✅ — Linux aarch64 🔄 🔄 🔄 — macOS — — 🔄 — Windows — — 🔄 🔄

    ✅ 已支持 | 🔄 计划中

    默认:release 二进制走 musl 全静态,Linux x86_64 可直接运行,无 glibc 依赖。

    文档 快速开始 — 5 分钟完成 install → new → build → run 示例项目 发布打包 工具链管理 从源码构建 mcpp.toml 指南 工作空间

    任意命令的完整选项可通过 mcpp <cmd> --help 查阅。

    AI 辅助学习:你可以将以下提示词发给 AI 编码助手,让它帮你快速了解 mcpp:

    阅读 https://github.com/mcpp-community/mcpp 仓库的 .agents/skills/mcpp-usage/SKILL.md 和 docs/ 目录下的文档, 告诉我如何用 mcpp 创建一个带依赖的 C++23 模块项目。 参与贡献

    欢迎通过 Issue 和 PR 参与项目开发。项目接受开发者使用 AI Agent 参与开发与贡献。

    基本流程

    创建 Issue — Bug 修复、新功能、优化等,先在 issues 创建讨论 实现改动 — Fork 仓库,创建分支,实现并验证(mcpp build + E2E 测试) 提交 PR — 使用 gh pr create,确保 CI 通过 CI 必须通过 — CI 不通过的 PR 不会被合入

    提交信息规范:feat: / fix: / test: / docs: / refactor: 前缀

    AI Agent 贡献:项目的 .agents/skills/mcpp-contributing/SKILL.md 提供了完整的 Agent 贡献流程和项目结构说明。将以下提示词发给 AI 助手即可:

    阅读 https://github.com/mcpp-community/mcpp 仓库的 .agents/skills/mcpp-contributing/SKILL.md , 按照指南帮我给 mcpp 项目提交一个贡献。 社区 & 生态 社区论坛 — 交流群 (Q: 1067245099) mcpp-index — 默认包索引 mcpplibs — 模块化 C++ 库集合 致谢

    项目依赖和灵感来源:

    xlings — 工具链 / 包管理底座 mcpplibs.cmdline — CLI 框架 ninja — 底层构建引擎 xmake — 跨平台构建工具 cargo — Rust 包管理器
  • 38 主题
    79 帖子
    DoomjustinD

    这是在学习了capy的隐式注入玩法后的更新。他的方案帮我梳理了我原本混乱的思路,感觉自己的目光都清撤了许多。

    旧版本的困局:stop_then 手动包装

    旧版本就是让用户手工拼装,每个 awaitable 都得用 stop_then 包一下:

    co_await stop_then(some_io_operation(), stop_token); // 得手动拼,十分繁琐

    这种操作确实实现了取消,但是所有的复杂性都暴露给了业务代码

    编写 co_await 时需要考虑"这个操作是否需要取消?" 如果需要,必须记得手动包裹 stop_then。 遗漏任何一个,就会产生"无法取消的操作",取消时挂起。

    值得一提的是,原本的实现由于混乱的思路引入了各种多线程问题。但是在梳理之后发现都是不必要处理的操作,属于是自己把自己坑进去了。

    新版本的改进:隐式注入取消 消除业务层污染

    旧方案的致命缺陷不在技术复杂度,而在于污染。业务代码里到处是 stop_then,每个 I/O 函数签名都要添加 std::stop_token 参数,这直接增加了维护成本和开发者的认知负担。

    // 旧方案:取消把整个调用链都污染了 Task<> handle_client(IOContext& ctx, std::stop_token token) { auto res = co_await stop_then(async_read(fd, buffer), token); // 得手动拼 if (!res && res.error() == std::errc::operation_canceled) { // 处理被取消 } }

    我原本认为用户可能需要自己选择可取消和不可取消,所以通过stop_then包装器可以提供最大的自由度。但是实际上,我想不出什么操作是不需要被取消的,甚至更进一步的想,一旦出现了不可取消的挂起操作,某种意义上其实可以算作一种资源泄露bug了。

    想法来源:从 capy 的实现中得到启发,我发现可以用 C++20 的 await_transform 重新设计这一层。capy 为了通用性,通过 env 注入 executor、allocator、stop_token 等参数;而我们的框架强绑定 io_uring,所以能更直接地在 promise 层注入 IOContext 和 stop_token,并统一依赖 ASYNC_CANCEL 语义。正因为这些都能确定下来,所以取消机制才能被完全自动化。

    核心改进:三层隐式注入

    第一层:Promise 环境绑定

    每个 Task 的 promise 自动持有当前的 IOContext 和 stop_token,无需用户处理:

    struct StoppablePromise { IOContext& context_; std::stop_token stop_token_; // ... };

    第二层:await_transform 统一拦截

    利用 C++20 的 await_transform 机制,框架可以在业务代码的每一个 co_await IOAwaiter 处进行拦截,自动将其升级为"取消感知"版本:

    // 新模型:零污染,框架自动处理 Task<> handle_client() { auto res = co_await async_read(fd, buffer); // 框架自动注入取消机制 if (!res && res.error() == std::errc::operation_canceled) { // 处理取消情况 } }

    第三层:统一落地语义

    StopTokenWrapper 在 awaiter 挂起期间隐式注册取消回调。被取消操作的最终结果由原操作的 completion 决定:

    也是在重新设计这块时,我发现并不需要处理 ASYNC_CANCEL 的 cqe,直接丢掉,反而让整个实现简单起来了,没有复杂的生命周期问题,也没有了被取消操作和取消操作的时序问题,被 resume 时一定是被取消操作的结果返回了,而且被取消操作的结果也是精确地,result会被正确设置。

    template<typename Promise> auto await_suspend(std::coroutine_handle<Promise> handle) noexcept -> std::coroutine_handle<> { auto inner_handle = inner_.await_suspend(handle, context()); // 核心:在挂起期隐式注册取消回调 if (promise_->stop_token.stop_possible()) stop_callback_.emplace(promise_->stop_token, CancelFn{ this }); return inner_handle; } // 取消触发时的统一路径 void CancelFn::operator()() noexcept { if constexpr (cancellable<Awaitable>) wrapper->inner_.cancel(wrapper->context()); // 若 Awaiter 实现 cancel,直接调用 else wrapper->context().cancel(wrapper->inner_); // 否则走 ASYNC_CANCEL } 修改后

    隐式注入:用户编写普通的 co_await,框架在后台自动升级为"取消感知"版本。

    生命周期简化:由于 cancel CQE 直接扔掉,不再跟踪,stop_callback 的生命周期严格绑定在 awaiter 挂起这段期间。不需要 shared_ptr<bool> 守卫了。

    awaiter 契约统一

    要是 awaiter 实现了 cancel(io_context) 方法,框架就调它。 否则用默认的 io_uring ASYNC_CANCEL 方式。 核心代码对比

    旧版本(用户手动拼):

    auto task = [](std::stop_token token) -> async::Task<> { co_await stop_then(some_io(), token); // 得手动传 token,手动调用 stop_then }; co_await async::any(task(group.stop_token()), ...);

    新版本(框架自动处理):

    auto task = []() -> async::Task<> { co_await some_io(); // 啥都不用干,框架已经全搞定 }; // stop_token 框架内部在 Task::promise 里已经绑了,用户看不见 多线程下的行为对比 旧版本 主线程 (IOContext.run()) ↓ [处理 I/O 完成事件A] ↓ task 协程被唤醒 ↓ stop_then 析构(alive_ = false) ↓ 后台定时器线程 ↓ [发送 stop_token cancel] ↓ post 任务进入队列 ↓ [IOContext 取出 post 任务,检查 alive_] ↓ *alive_ == false,安全丢弃

    陷阱:如果 post 任务执行前,协程没析构,就访问了已 free 的内存。

    新版本 主线程 (IOContext.run()) ↓ task 协程开始 co_await some_io() ↓ await_transform 自动把它包装成 StopTokenWrapper ↓ stop_callback 被注册在 wrapper 的栈帧上 ↓ 后台定时器线程 ↓ [stop_token 被触发] ↓ stop_callback 立刻在这个线程上执行(标准库保证线程安全) ↓ 调用 awaiter.cancel(context) 把 cancel 指令 post 回主线程 ↓ 主线程 (IOContext.run()) ↓ [从事件循环取出 cancel 指令] ↓ 发送 ASYNC_CANCEL SQE 给内核 ↓ 原操作的 CQE 返回 -ECANCELED 或原结果 ↓ awaiter 恢复协程,返回错误给上层

    如此一来:

    stop_callback 本身线程安全(标准库保证)。 cancel 动作在 IOContext 里执行,安全、可控。 栈帧生命周期自动保护,不需要 shared_ptr 守卫。 结尾

    由于这个修改算是撬了最底层的设计,所以导致绝大部分的模块都需要重写。不过在理清了混乱之后,所有的实现相较于旧版本都变得简单了很多,甚至绝大部分的Awaiter实现都变成了下面这种极简版本,也就是继承一下IOAwaiter,写个 prepare()和构造函数就完事了。

    class SendAwaiter : public async::IOAwaiter<SendAwaiter, std::size_t> { private: int fd_; std::span<const std::byte> buffer_; public: SendAwaiter(int fd, std::span<const std::byte> buffer) : fd_{ fd } , buffer_{ buffer } {} void prepare(::io_uring_sqe* sqe) const noexcept { ::io_uring_prep_send(sqe, fd_, buffer_.data(), buffer_.size(), MSG_NOSIGNAL); } };
  • 一个技术知识分享、学习、交流的社区

    16 主题
    54 帖子
    sunrisepeakS

    @dustchens 已支持

  • Got a question? Ask away!

    4 主题
    14 帖子
    SPeakS

    备注一下使数学公式的使用语法

    单行公式语法 - $ 你的公式 $

    $ log_2^n $

    $ log_2^n $

    多行公式语法 - $$ 你的公式 $$

    $$ log_2^n => log_2^9 = 3 , n = 9 $$

    $$
    log_2^n =>
    log_2^9 = 3, n = 9
    $$

公告栏 | Bulletin Board

欢迎加入d2learn社区 - 社区指南
Welcome to the d2learn Community - Community Guide

一个以 [知识、技术、代码、项目、想法、开源] 相关话题为主导的社区
A community focused on topics related to [knowledge, technology, code, projects, ideas, and open source].


在线用户