第十三章 C++23 新特性详解

← 返回目录

本章目标:std::print/println、std::expected、std::mdspan(了解)、 std::flat_map(了解)、deducing this(了解)、 其他实用小特性。C++23 是"补全体验"的版本, 没有颠覆性大特性,但每天都在改善你的代码。

13.1 std::print / std::println(已在第一章用到,重点)

#include <print>
std::println("你好,{}!", name);
std::print("不换行输出");

自 C++23 起,新代码的输出一律用 print/println。

13.2 std::expected:比异常更优雅的错误处理(重要)

#include <expected>
enum class Err { BadInput, OutOfRange };

std::expected<int, Err> parse_int(std::string_view s) {
    int v = 0;
    // ... 解析逻辑 ...
    if (解析失败) return std::unexpected(Err::BadInput);
    return v;
}

auto r = parse_int("42");
if (r) {
    std::println("值是 {}", *r);       // 正常分支
} else {
    std::println("出错了:{}", r.error());   // 错误分支
}
// 也可以变换:
auto doubled = r.transform([](int x){ return x * 2; });  // 成功则变换
auto handled = r.value_or(-1);         // 失败给默认值

人话:expected<T, E> = "要么是 T,要么是 E(错误)"。 和 optional 的区别:optional 只知道"有没有", expected 还告诉你"为什么没有"。

适用场景:函数可能失败,且调用方通常需要处理错误。 (比异常快、比错误码安全、可组合。详见第十五章。)

13.3 std::mdspan:多维数组视图(了解)

#include <mdspan>
std::vector<double> data(24);
std::mdspan matrix(data.data(), 4, 6);   // 把一维数据当 4x6 矩阵
matrix[2, 3] = 1.5;                       // 下标语法

知道即可,用到再学。

13.4 std::flat_map / flat_set(了解)

#include <flat_map>
std::flat_map<std::string, int> m;
m["a"] = 1;                       // 用法和 map 几乎一样

和 map 的区别:

查找 O(log n) 但缓存命中率高,小数据量大胜出

结论:数据量小(几百上千)且读多写少 → flat_map 更快。 游戏开发常用它。

13.5 deducing this(理解即可)

以前成员函数里的 this 是固定类型:

struct Widget {
    void foo() const;      // this 是 const Widget*
};

复制粘贴 const/& 版本非常烦人。

C++23:给 this 起名字,让它自动推断:

struct Widget {
    void foo(this auto&& self) {    // 自动匹配 const/&
        // self 就是对象自己,类型自动
    }
};

好处:

  1. 一个函数通吃 const 和非 const 版本
  2. 递归 lambda(lambda 里调自己)
  3. CRTP(奇异递归模板)可以简化

知道这是"给 this 起名"就行,高级用法按需学。

13.6 更多 C++23 小特性(扫一遍留印象)

  for (auto [x, y] : std::views::zip(v1, v2)) { ... }
  for (auto [i, x] : std::views::enumerate(v)) { ... }

你的 Termux 版暂时没有,用 accumulate 替代,见下节注释)

13.7 C++23 实战示例:现代感拉满

#include <print>
#include <ranges>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> names{"bob", "alice", "carol", "dave"};

    // 打印带编号的名字(enumerate 是 C++23,
    // 你的 Termux libc++ 暂时没有,用下标循环代替,效果一样)
    for (std::size_t i = 0; i < names.size(); ++i) {
        std::println("{}. {}", i + 1, names[i]);
    }

    // 过滤 + 变换 + 收集成新 vector
    // (std::ranges::to<vector> 也暂缺,手动构造即可)
    std::vector<std::string> long_names;
    for (const auto& s : names
            | std::views::filter([](const std::string& s) {
                   return s.size() >= 4;
               })) {
        long_names.push_back(s);
    }
    std::println("有 {} 个长名字", long_names.size());
}

注:enumerate / fold_left / ranges::to 是 C++23 新增, 新版 libc++(clang 19+ 自带)才完整。你的 Termux 环境 libc++ 版本略旧,本书示例已全部换成等价写法, 等系统更新后可直接换回新写法(语法见 13.6)。

13.8 从 C++17 到 C++23 的代码风格进化(总结)

C++17 时代:optional、结构化绑定、filesystem、if constexpr C++20 时代:ranges 管道、concepts、三路比较、span、chrono C++23 时代:print、expected、fold、zip/enumerate、to<vector>

现代 C++(17+)写代码的固定姿势:

  1. 输出用 println,绝不用 printf
  2. 容器 + 算法 + lambda + ranges,能不写循环就不写
  3. 错误处理:先 expected,再考虑异常
  4. 所有权:unique_ptr/shared_ptr + RAII,不碰 new/delete
  5. 类型:auto 推断,{} 初始化,const 能加就加

本章小结

练习题


  1. 用 expected<int, Err> 写一个除法函数(除以 0 返回错误)。
  2. 用两个下标循环同时遍历两个 vector 并打印对应元素

(等价于 views::zip 的效果)。

  1. 打印带行号的文件内容(每行一个 string,下标循环即可)。
  2. 用 accumulate 求 vector 的平均值(求和 + size)。
  3. 把 13.7 的示例改成用 accumulate 计算长名字的平均长度。