C++26:更多函数包装器

Lobsters Hottest 工具

摘要

C++26 引入了两个新的函数包装器:std::copyable_function(提供了可复制且 const 正确的 std::function 替代品)和 std::function_ref(一个非拥有、可调用的引用,具有引用语义)。

<p><a href="https://lobste.rs/s/wqeqgc/c_26_more_function_wrappers">评论</a></p>
查看原文
查看缓存全文

缓存时间: 2026/05/20 16:29

# C++26:更多函数包装器 来源:https://www.sandordargo.com/blog/2026/05/20/cpp26-copyable-function C++26 继续填补我们在类型擦除的可调用包装器故事中的空白。自 C++11 以来我们已有 `std::function`,自 C++23 以来有 `std::move_only_function`,但仍有一些缺失的部分。现在我们迎来了两个新成员:`std::copyable_function` 和 `std::function_ref`。 ## `std::function` 有什么问题? `std::function` 一直为我们服务得很好,但它有两个众所周知的问题。首先,它会显著增加二进制大小 (https://www.sandordargo.com/blog/2023/04/05/binary-size-and-templates)。其次,也是更根本的问题,它存在一个**常量正确性缺陷**。它的 `operator()` 被声明为 `const`,但它可以调用存储的可调用对象的非 `const` `operator()`: ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 // https://godbolt.org/z/9YxcW5eqK #include <iostream> #include <functional> struct Counter { int counter = 0; void operator()() { ++counter; std::cout << "modifying state: " << counter << '\n'; } }; int main() { const std::function<void()> f = Counter{}; f(); // OK (!) } ``` 这个缺陷根植于原始设计,无法在不破坏 ABI 的情况下修复。C++23 引入了 `std::move_only_function` (https://www.sandordargo.com/blog/2024/01/31/cpp23-likes-to-move-it) (P0288R9 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0288r9.html)),它修复了常量正确性问题,并增加了对 cv/ref/noexcept 限定符的支持。但正如其名,它不可复制。我们仍需一个既可复制又**常量正确**的包装器。 ## `std::copyable_function` P2548R6 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2548r6.pdf) 恰恰填补了这一空白。其设计紧跟 `std::move_only_function`,增加了拷贝构造函数和拷贝赋值运算符,并要求存储的可调用对象是可拷贝构造的。与 `std::function` 的关键区别在于:签名中的限定符直接控制 `operator()` 如何声明: ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // https://godbolt.org/z/orxPK9Gn9 #include <iostream> #include <functional> struct Counter { int count = 0; int operator()() { return ++count; } // non-const }; int main() { // copyable_function 意味着 operator() 是非 const 的 std::copyable_function<int()> f = Counter{}; f(); // OK,非 const 调用 // 如果你想要 const,需要显式声明: // std::copyable_function<int() const> g = Counter{}; // 错误! // Counter::operator() 没有 const 限定 std::copyable_function<int() const> h = []{ return 42; }; // OK } ``` 和 `move_only_function` 一样,它支持完整的限定符组合——`const`、`noexcept`、左值/右值引用限定符,以及它们的任意组合。这比 `std::function` 有了显著改进,后者不支持这些限定符。 `copyable_function` 可以隐式转换为 `move_only_function`,反之则不行。和 `move_only_function` 一样,它省略了很少使用的 `target()` 和 `target_type()` 成员函数。 ## `std::function_ref` P0792R14 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0792r14.html) 添加了一种不同类型的包装器:一个**非拥有的、类型擦除的可调用引用**。可以把它理解为可调用对象的 `std::string_view`。 ``` 1 2 3 4 5 6 7 8 9 // 使用函数指针——限制太大,不支持带捕获的 lambda: payload retry(size_t times, payload(*action)()); // 使用模板——可行但膨胀二进制,必须在头文件中: template<typename F> payload retry(size_t times, F&& action); // 使用 function_ref——轻量级,非拥有,无分配: payload retry(size_t times, std::function_ref<payload()> action); ``` 它具有**引用语义**:没有默认构造函数,没有 `operator bool`,也没有 `nullptr` 比较。`function_ref` 始终指向一个有效的可调用对象。每个特化都是可平凡复制的,意味着它可以按寄存器传递。禁止从非函数类型赋值,以防止悬挂引用——因为 `function_ref` 不拥有可调用对象,赋值一个临时对象将是 bug。 它支持 `const` 和 `noexcept` 限定符,但不支持引用限定符,因为引用类型上的引用限定符没有意义。后续论文 P3961R1 (https://isocpp.org/files/papers/P3961R1.html) 修复了从另一个 `function_ref` 构造时的双重间接问题,并允许将 `noexcept` 限定的 `function_ref` 赋值给非 `noexcept` 的 `function_ref`——正如你对普通函数指针的预期。 ## 选择正确的包装器 现在有四种可调用包装器可供选择,以下是一个快速指南: - **`function_ref`**:用于回调参数。非拥有,零开销。 - **`move_only_function`**:用于存储不需要复制的可调用对象。任务队列,延迟执行。 - **`copyable_function`**:用于存储需要副本的可调用对象。现代 `std::function` 替代品。 - **`std::function`**:遗留。新代码中避免使用。 ## 结论 C++26 完成了类型擦除的可调用包装器的图景。`std::copyable_function` 提供了 `std::function` 从一开始就应该有的样子:一个常量语义正确的可复制包装器。`std::function_ref` 填补了非拥有场景的空白,为回调参数提供了一种轻量级、零分配的替代方案。与 `std::move_only_function` 一起,新代码中再也没有理由使用 `std::function` 了。 ## 更深入的联系 如果你喜欢这篇文章,请: - 点赞, - 订阅我的通讯 (https://sandor-dargo.kit.com/e19f29b0a1) - 并在 Twitter 上与我联系 (https://twitter.com/SandorDargo)!

相似文章

C++26 中 std::format 的改进

Lobsters Hottest

C++26 标准对 std::format 库进行了多项改进,包括直接指针格式化、路径格式化、constexpr 支持,以及为 std::println 新增的空行重载。

C++26:标准库强化

Lobsters Hottest

C++26 引入了标准化的库强化机制,用于在运行时捕获常见的未定义行为(如越界访问)。基于 Google 的生产经验,此举仅带来 0.30% 的性能开销,同时将段错误减少了 30%。

C++ 标准库在过去十五年间一直在自我撤步,证据公开

Lobsters Hottest

一份详细的目录,列出了从 C++11 到 C++26 期间被正式弃用、非正式不推荐或由于 ABI 约束实际上已损坏但无法修复的 C++ 标准库特性。文章指出,C++ 委员会推出一系列替代品来替换其自身特性的模式始终如一,其中包含一个基准测试,显示 Rust 和 C++ 标准库容器之间的 P99 延迟差异高达 58 倍。

C++ 编译器何时可以反虚拟化调用?

Hacker News Top

探讨 C++ 编译器何时可以对虚函数调用进行去虚拟化,涵盖已知动态类型和 final 关键字等情况,并在 GCC、Clang、MSVC 和 ICC 之间进行比较。