C++ 23 相比 C++ 20 新增之特征

目录

[1. 新增语言特征](#1. 新增语言特征)

[1.1 显式 this 对象参数 ( 推导 this )](#1.1 显式 this 对象参数 ( 推导 this ))

[1.2 if consteval](#1.2 if consteval)

[1.3 多维下标运算符](#1.3 多维下标运算符)

[1.4 静态函数调用,静态下标运算符,以及静态 lambda](#1.4 静态函数调用,静态下标运算符,以及静态 lambda)

[1.5 更简隐式移动](#1.5 更简隐式移动)

[1.6 auto(x) 和 auto{x}](#1.6 auto(x) 和 auto{x})

[1.7 新的预处理指令](#1.7 新的预处理指令)

[1.8 延长界域 for 循环中某些临时对象的生命期](#1.8 延长界域 for 循环中某些临时对象的生命期)

[1.9 新属性 [[assume(expression)]]](#1.9 新属性 [[assume(expression)]])

[1.20 基于继承构造函数的类模板参数推导](#1.20 基于继承构造函数的类模板参数推导)

[1.21 复合语句末尾的标签](#1.21 复合语句末尾的标签)

[1.22 初始化语句中的别名声明](#1.22 初始化语句中的别名声明)

[1.23 size_t 及其对应有符号类型的字面量后缀:zu(例如 0zu、15zu)](#1.23 size_t 及其对应有符号类型的字面量后缀:zu(例如 0zu、15zu))

[1.24 带字面量的扩展浮点类型(编译器有条件支持)](#1.24 带字面量的扩展浮点类型(编译器有条件支持))

[1.25 来自零元(nullary) lambda 表达式的 optional ()](#1.25 来自零元(nullary) lambda 表达式的 optional ())

[1.26 lambda 表达式上的属性](#1.26 lambda 表达式上的属性)

[1.27 constexpr 的变化](#1.27 constexpr 的变化)

[1.28 窄化 static_assert 和 if constexpr 中的上下文到 bool 的转换](#1.28 窄化 static_assert 和 if constexpr 中的上下文到 bool 的转换)

[1.29 在行拼接前去除空白字符](#1.29 在行拼接前去除空白字符)

[1.30 强制规定声明顺序布局](#1.30 强制规定声明顺序布局)

[1.31 定界转义序列](#1.31 定界转义序列)

[1.32 命名通用字符转义](#1.32 命名通用字符转义)

[1.33 文本编码的变化](#1.33 文本编码的变化)

[1.34 部分关键词被赋予了新的含义,例如 this](#1.34 部分关键词被赋予了新的含义,例如 this)

[2. 库特征](#2. 库特征)

[2.1 标准库增加模块支持](#2.1 标准库增加模块支持)

[2.2 增加协程库支持](#2.2 增加协程库支持)

[2.3 泛型工具支持](#2.3 泛型工具支持)

[2.4 编译时支持](#2.4 编译时支持)

[2.5 迭代器,界域(ranges) 和算法支持](#2.5 迭代器,界域(ranges) 和算法支持)

[2.6 内存管理支持](#2.6 内存管理支持)

[2.7 字符串和文本处理的支持](#2.7 字符串和文本处理的支持)

[2.8 诊断支持](#2.8 诊断支持)

[2.9 I/O 支持](#2.9 I/O 支持)

[2.10 容器支持](#2.10 容器支持)

[2.11 C 兼容支持](#2.11 C 兼容支持)


1. 新增语言特征

1.1 显式 this 对象参数 ( 推导 this )

允许你显式命名成员函数中原本隐式的对象参数。其声明方式是在非静态成员函数的首个参数之前放置关键字 this 。例如:

cpp 复制代码
struct Example {
    // 旧的方式 (隐式 this)
    void old_style() { /* use this->member */ }

    // 新方式 (显式对象参数)
    void new_style(this Example& self) { 
        self.member = 10; 
    }
    
    int member;
};

1.2 if consteval

if consteval 是一条控制流语句,用于检测一段代码块是在编译时(即常量求值阶段)还是运行时执行 。它取代了 C++20 中较旧的std::is_constant_evaluated(),通过修正后者在特定边缘情况下可能产生误导的问题,从而提升了准确性。例如:

cpp 复制代码
#include <iostream>

// 此函数可以在编译时和运行时调用
constexpr int compute(int x) {
    if consteval {
        // 此分支仅在常量求值过程中运行 (编译时)(常量求值在编译时即可进行)
        return x * x; 
    } else {
        // 此分支仅在运行时执行
        return x + x; 
    }
}

int main() {
    // 1. 编译时求值
    constexpr int a = compute(10); // a is 100
    
    // 2. 运行时求值
    int input = 10;
    int b = compute(input);        // b is 20
    
    std::cout << "Compile-time: " << a << "\nRuntime: " << b;
}

1.3 多维下标运算符

该运算符可定义为接受任意数量的参数(甚至可以是变参的),从而允许使用 **a[i, j, k] = x;**的形式,而非 **a[i][j][k] = x;**的形式。例如:

cpp 复制代码
template<typename T, std::size_t Rows, std::size_t Cols>
struct Matrix {
    T data[Rows * Cols];

    // C++23 multidimensional subscript operator
    T& operator[](std::size_t r, std::size_t c) {
        return data[r * Cols + c];
    }
};

int main() {
    Matrix<int, 3, 3> mat;
    mat[1, 2] = 42; // 直观多维下标访问
}

1.4 静态函数调用,静态下标运算符,以及静态 lambda

C++23 引入了一项新特性,允许将函数调用运算符operator() )和下标运算符(operator[] )声明为静态成员函数。这一变更同样适用于 lambda 表达式,现在可以显式地为其加上 static说明符。例如:

cpp 复制代码
struct ConstantLookup {
    static int operator()(int x) { return x * 2; }
    static int operator[](int x, int y) { return x + y; }
};
cpp 复制代码
// C++23 static lambda
auto add = [](int a, int b) static { return a + b; };

// 错误: 不能捕 static lambda
int offset = 10;
auto fail = [offset](int x) static { return x + offset; }; 

1.5 更简隐式移动

C++23 引入了"更简单的隐式移动"(通过 P2266R3),它优化了编译器处理返回局部变量和参数的方式。在 C++23 下,隐式使用了移动语义无需使用 std::move。例如:

cpp 复制代码
struct Widget {
    Widget(Widget&&) = default;
    Widget(const Widget&) = delete; //仅移动类型
};

Widget example(Widget w) {
    return w; // 隐式移动 (C++11 及以后版本有效)
}

Widget handle_ref(Widget&& w) {
    return w; // C++23 下的隐式移动 (前面的版本需要使用 std::move)
}

1.6 auto(x) 和 auto{x}

**auto(x)**是 C++23 引入的一种用于执行"衰变复制"(decay-copy)的函数式转换,剥离引用和​​ const限定符(正如按值传递那样),并将结果强制转换为纯右值 ;而 **auto{x}**则是 C++11 引入的统一初始化语法。两者执行的操作截然不同。

例如:对于 auto(x) :

cpp 复制代码
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {10, 20, 30};
    
    // x 是指向 vector 中第一个元素的常量引用
    const int& ref = vec.front(); 
    
    // auto(ref) 创建了一个分叉的 new , 可修改整数副本
    auto copy = auto(ref); 
    copy += 5; // 修改了副本不影响 vec.front()
    
    std::cout << ref << " vs " << copy << "\n"; // Outputs: 10 vs 15
}

对于 auto{x}

cpp 复制代码
#include <iostream>

template <typename T>
void processValue(T val) {
    // 推断 val 的类型并利用完全相同的类型初始化 'result'
    auto result{val}; 
    
    std::cout << "Processed: " << result << "\n";
}

int main() {
    processValue(42);    // 推断为 auto{42} -> int
    processValue(3.14);  // 推断为 auto{3.14} -> double
}

1.7 新的预处理指令

#elifdef#elifndef

例如:

cpp 复制代码
#ifdef MACRO_A
    // Code if MACRO_A is defined
#elifdef MACRO_B
    // Code if MACRO_A is NOT defined, but MACRO_B IS defined
#else
    // Code if neither is defined
#endif
cpp 复制代码
#ifdef VERSION_A
    // Code for Version A
#elifndef VERSION_B
    // Code if VERSION_A is not defined AND VERSION_B is also not defined
#else
    // Code if VERSION_A is not defined BUT VERSION_B is defined
#endif

#warning

#warning 用于在编译过程中发出自定义的警告信息,且不会中断编译过程。该指令于 C++23 标准中正式确立,尽管像 GCC 和 Clang 这样的许多编译器早已将其作为扩展功能予以支持。

例如:

cpp 复制代码
#warning "This is a custom compilation warning message"

1.8 延长界域 for 循环中某些临时对象的生命期

**现在,在界域 for 循环的范围表达式内创建的所有临时对象的生命周期,已被延长至循环结束为止。**这项由 P2718R0 引入的变更,解决了长期以来存在的中间临时对象可能过早销毁、从而导致未定义行为的问题。

1.9 新属性 [[assume(expression)]]

**它向编译器传达:"在此处,该表达式总是为真;请利用这一信息来生成更优化的代码。"**它将 __assume (MSVC) 或 __builtin_assume (Clang) 等特定于厂商的扩展进行了标准化,从而协助编译器在无需进行运行时检查的情况下执行优化。

例如:

cpp 复制代码
void process(int x) {
    // 告之 'x' 总是正数
    // 它现在能够优化掉任何针对负数的下游检查。
    [[assume(x > 0)]]; 
    
    // ... 函数其它部分 ...
}

1.20 基于继承构造函数的类模板参数推导

例如:

cpp 复制代码
template <typename T>
struct Base {
    Base(T value) : data(value) {}
    T data;
};

template <typename T>
struct Derived : Base<T> {
    using Base<T>::Base; // 继承构造函数
};

int main() {
    // 在 C++20 下: 编译失败 (无法推导 T)
    // 在 C++23 下: 自动推导 Derived<int>
    Derived d(42); 
}

1.21 复合语句末尾的标签

在 C++ 23 中,允许标签后面省略 ";" 号, 例如:

cpp 复制代码
void process(int x) {
    if (x < 0) goto END;
    // ... logic ...
    x = 42;
END: 
} // 标签 "END:" 位于代码块的最末尾

而在之前的标准中,不能省略 ";" 号:

cpp 复制代码
label: ; 

1.22 初始化语句中的别名声明

例如:

cpp 复制代码
for (using T = int; T e : container) {
    // T 在这儿可用作 int 的别名
}

1.23 size_t及其对应有符号类型的字面量后缀:zu(例如 0zu、15zu)

例如:

cpp 复制代码
auto size = 42uz;

1.24 带字面量的扩展浮点类型(编译器有条件支持)

C++23 引入了扩展浮点类型及专用的字面量后缀,从而提供了一种标准化的方式来使用特定的 IEEE 754 格式,例如 16 位或 128 位浮点数。

下表总结了新类型及其对应的后缀:

1.25 来自零元(nullary) lambda 表达式的 optional ()

在 C++23 下,对于无参(即零元) lambda 表达式,你可以完全省略其后的空括号 ()。这一规则使你能够编写出整洁、简洁的 lambda 表达式(即便是在使用 mutablenoexcept或尾置返回类型等修饰符的情况下), 而在此前,这些修饰符往往强制要求你必须保留那对括号。

在 C++ 23 之前:

cpp 复制代码
// 在 C++20 下,强制要求 () , 因为有 mutable 修饰
auto my_lambda = []() mutable noexcept -> void {
    // 函数体
};

在 C++ 23 下:

cpp 复制代码
// () 完全可忽略
auto my_lambda = [] mutable noexcept -> void {
    // 函数体
};

// 甚至更简:
auto print_hello = []{ std::cout << "Hello!\n"; };

1.26 lambda 表达式上的属性

在 C++23 中,lambda 表达式上属性的使用得到了显著的澄清与扩展,从而允许将其更直接地应用于 lambda 的调用运算符。

例如:

cpp 复制代码
// C++23: 应用到运算符 () 上的属性
auto lambda = [] [[nodiscard]] () { return 42; }

1.27 constexpr的变化

constexpr函数中的非字面量变量、标签和 goto 语句

允许在 constexpr函数中使用可用于常量表达式的 staticthread_local变量

constexpr函数的返回类型和参数类型不再要求必须是字面量类型

现在可以编写这样一种 constexpr函数:其任何一次调用均无法满足"核心常量表达式"的要求

1.28 窄化 static_assertif constexpr中的上下文到 bool的转换

在 C++23 中,static_assertif constexpr的规则得到了放宽,允许进行窄化的上下文转换为 bool类型。这一变更由提案 P1401R5 引入,旨在确保这些上下文中的常量表达式能够像其运行时对应物(即标准的 if语句)那样运作------而标准的 if 语句历来都是允许此类转换的。

1.29 在行拼接前去除空白字符

在 C++23 中,标准进行了更新,正式要求在执行行拼接之前移除尾随空白字符。

1.30 强制规定声明顺序布局

(1) 成员变量的内存布局

C++ 标准严格规定:类的非静态数据成员在内存中的布局顺序与它们在类定义中的声明顺序一致 (先声明的成员地址较低)。即使将它们放在不同的 public/private 访问块中,编译器也必须按声明顺序排列,并且不能混排以优化空间。

(2) 初始化顺序(构造函数)

成员变量的初始化顺序完全由声明顺序决定,与构造函数的初始化列表中的顺序无关。为了避免混淆,强烈建议初始化列表的顺序与声明顺序保持一致。

例如:

cpp 复制代码
#include <iostream>

class Widget {
private:
    int b; // 声明在先:在内存中先分配
    int a; // 声明在后:在内存中后分配

public:
    // 尽管初始化列表里先写 a 后写 b,
    // 但实际运行时,b 会先被初始化,a 后被初始化。
    Widget(int val) : a(val), b(a * 2) {}

    void printAddresses() {
        std::cout << "变量 b 的地址: " << &b << "\n";
        std::cout << "变量 a 的地址: " << &a << "\n";
    }
};

int main() {
    Widget w(5);
    w.printAddresses(); 
    // 输出结果中,b 的地址总是低于 a 的地址。
    return 0;
}

1.31 定界转义序列

C++23 引入了定界转义序列,旨在防止字符 "溢出" 并侵入紧随其后的字符。通过使用花括号 **{}**显式界定序列的起止位置,开发者不再需要依赖字符串拼接手段,即可将其与后续的数字或字母隔离开来。

主要类型及语法:

(1) 十六进制定界转义序列\x{十六进制序列}

旧方式\x123 (可能会引发超出范围的错误)

新方式\x{12 3} 会分别被识别为两个不同的独立符号,必须写成 \x{123}

(2) 八进制定界转义序列\o{八进制序列}

使用 \o 开头配合花括号包围八进制数字(范围 07),避免了传统 \012 长度限制带来的歧义

(3) Unicode 定界转义序列\u{十六进制序列}

比起传统的 \uXXXX (固定4位),支持任意长度的 Unicode 码点,例如 \u{1F600}

1.32 命名通用字符转义

C++23 通过提案 P2071R2 引入了"命名通用字符转义"(Named Universal Character Escapes),允许你通过 Unicode 字符的官方名称来引用它们,而不仅仅是使用其十六进制代码。

新的转义序列采用**\N{name}**格式:

\N:命名字符的引述符。{name}:官方 Unicode 字符名称或已批准的别名(控制别名、更正别名或替代别名)。

1.33 文本编码的变化

(1) 支持 UTF-8 作为一种可移植的源文件编码

(2) 一致的字符字面量编码

(3) 字符集与编码

1.34 部分关键词被赋予了新的含义,例如 this

2. 库特征

2.1 标准库增加模块支持

标准库 modules std 和 std.compat

2.2 增加协程库支持

用于范围的同步协程 std::generator

2.3 泛型工具支持

结果类型 std::expected

std::optionalstd::expected 的一元(monadic)运算

工具函数 std::to_underlying ,用于获取枚举类型的底层值

仅可移动的可调用对象包装器 td::move_only_function

std::forward_like

std::invoke_r

std::bind_back

std::byteswap

std::unreachable:用于标记不可达代码的函数

使 std::tuple与其他类元组对象兼容

针对 std::reference_wrapper 的 std::basic_common_reference 特化,产生引用类型

std::pair的转发构造函数添加默认参数

2.4 编译时支持

(1) constexpr****对下列内容的支持:

std::type_info::operator==

std::bitset

** **std::unique_ptr

对某些 <cmath> 函数

针对 std::to_charsstd::from_chars 的整型重载

(2) 逾编程工具:

类型特征 std::is_scoped_enum , std::is_implicit_lifetime,std::reference_constructs_from_temporary , 和 std::reference_converts_from_temporary

(3) `` ``为比较概念添加仅可移动类型的支持。

2.5 迭代器,界域(ranges) 和算法支持

(1) 新的界域转换函数 std::ranges::to

(2) 新约束界域算法

std::ranges::starts_with

std::ranges::ends_with

std::ranges::contains

std::ranges::contains_subrange

std::ranges::find_last和其它变体

iotashift_leftshift_right的界域版本

界域折叠算法

(3) 新的 std::ranges::range_adaptor_closure (用于定义用户自定义范围适配器闭包的辅助工具)

(4) 新的界域适配器

std::views::zip 及其它变体

std::views::adjacent及其它变体

std::views::join_with

std::views::slide

std::views::chunk

std::views::chunk_by

std::views::as_rvalue

std::views::as_const

std::views::repeat

std::views::stride

views::cartesian_product

std::views::enumerate

(5) 关于修正常量迭代器、哨兵及界域------即std::ranges::cbegin及其他返回常量迭代器的类似工具------应当确保其功能在面对"浅层常量"(shallow-const)视图(例如 std::span)时也能得到完​​全的保障。

(6) 将界域迭代器作为非界域算法的输入

(7) 放宽界域适配器,允许仅移动类型

(8) 将某些视图的多参数构造函数 explicit 化

2.6 内存管理支持

(1) 用于 C 语言互操作性的 std::out_ptrstd::inout_ptr

(2) std::allocate_at_leaststd::allocator::allocate_at_least

(3) 针对隐式生命周期类型的显式生命周期管理函数 std::start_lifetime_as

(4) 禁止用户对 std::allocator_traits 进行特化

2.7 字符串和文本处理的支持

(1) 字符串类中的新成员函数和变化

std::basic_string_view::contains 和**std::basic_string::contains**

禁用 std::basic_stringstd::basic_string_viewnullptr进行构造

std::basic_string_viewexplicit界域构造函数

std::basic_string::resize_and_overwrite

std::basic_string::substr的右值引用重载,用于高效分片

(2) 格式化界域、元组、字符与字符串的转义表示、std::thread::id 以及栈追踪。

2.8 诊断支持

栈追踪库,头文件**<stacktrace>及类std::stacktrace**

2.9 I/O 支持

来自新头文件**<print>的格式化输出函数 std::printstd::println**

spanstream 库(基于 std::span 的字符串流),来自新头文件 <spanstream>

std::fstreams中的独占模式的支持

std::basic_ostream::operator<<(const volatile void*)

2.10 容器支持

多维扩展 std::mdspan

来自其他兼容系列的容器的可构建性与可分配性

flat_set 和 flat_map 容器适配器

容器推导指引中分配器的非推导上下文

关联容器的异构擦除重载

允许在栈和队列中通过迭代器对进行构造

要求std::spanstd::basic_string_view具有平凡可复制性

2.11 C 兼容支持

新头文件 <stdatomic.h>。该头文件是并发支持库的一部分。

相关推荐
AI人工智能+电脑小能手4 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试
水木流年追梦4 小时前
大模型入门-大模型分布式训练2
开发语言·分布式·python·算法·正则表达式·prompt
sali-tec4 小时前
C# 基于OpenCv的视觉工作流-章78-KRT测量
图像处理·人工智能·数码相机·opencv·算法·计算机视觉
菜菜的顾清寒4 小时前
力扣HOT100(32)二叉树的中序遍历
数据结构·算法·leetcode
x2c4 小时前
数据结构:线性表中链表的建立和基本操作(C)
算法
DolphinDB4 小时前
基于 DolphinDB 搭建微服务的 SpringBoot 项目
后端·算法
口袋里のInit4 小时前
基础知识——ARM M核入栈出栈流程
开发语言·arm开发
罗超驿4 小时前
5.Java线程创建全攻略:5种写法 + 高频面试题解析
java·开发语言·java-ee
珊瑚里的鱼5 小时前
【动态规划】第N个泰波那契数
算法·动态规划
Simon523145 小时前
反射------5.26学习小计
java·开发语言·spring boot