C++23 常用特性
一、核心语言特性
1.1 deducing this
1.1.1 简化 CRTP 模式
CRTP(Curiously Recurring Template Pattern)是一种常用设计模式,传统实现需要手动传递派生类类型:
// C++20 及之前
template<typename Derived>
struct Base {
void func() {
static_cast<Derived*>(this)->impl(); // 手动转换 this
}
};
struct Derived : Base<Derived> {
void impl() { /* 实现 */ }
};
C++23 通过 deducing this 自动推导派生类类型:
// C++23
struct Base {
// this 作为模板参数,自动推导 Self 为 Derived
template<typename Self>
void func(this Self&& self) {
self.impl(); // 直接调用派生类 impl,无需 static_cast
}
};
struct Derived : Base {
void impl() {
std::cout << "Derived::impl" << std::endl;
}
};
1.1.2 成员函数模板推导
deducing this 允许成员函数根据 this 的类型(左值/右值)推导模板参数:
struct MyClass {
// 根据 this 是左值还是右值,推导 Self 的类型
template<typename Self>
auto get(this Self&& self) {
if constexpr (std::is_lvalue_reference_v<Self>) {
return std::ref(self.data); // 左值返回引用
} else {
return std::move(self.data); // 右值返回移动后的值
}
}
private:
std::string data = "hello";
};
1.2 constexpr 增强
1.2.1 constexpr for 循环
C++23 允许 for 循环在 constexpr 函数中使用,支持编译期迭代:
constexpr auto sum(std::initializer_list<int> list) {
int s = 0;
for (int x : list) { // constexpr for 循环
s += x;
}
return s;
}
consteval int s = sum({1, 2, 3}); // 编译期计算 s=6
1.2.2 constexpr 动态内存管理优化
C++23 放松 constexpr 中动态内存的限制,允许 new/delete 在编译期更灵活使用:
constexpr std::vector<int> create_vector() {
std::vector<int> v;
v.reserve(3);
v.push_back(1);
v.push_back(2);
v.push_back(3);
return v;
}
constexpr auto vec = create_vector(); // 编译期创建 vector
1.3 if consteval
1.3.1 编译期与运行期分支
if consteval 判断当前上下文是否为编译期常量表达式,选择执行分支:
consteval int compile_time() {
return 42;
}
int run_time() {
return 42;
}
auto func(bool flag) {
if consteval {
return compile_time(); // 编译期上下文执行
} else {
return run_time(); // 运行期上下文执行
}
}
int main() {
int a = func(true); // 调用 compile_time()(编译期)
int b = func(false); // 调用 run_time()(运行期)
return 0;
}
1.3.2 与 constexpr if 的区别
if consteval:判断上下文是否为编译期(如 consteval 函数内),分支在编译期确定。constexpr if:判断条件是否为编译期常量,分支在编译期静态选择(未选中分支不生成代码)。
1.4 其他语言特性
1.4.1 static operator[]
静态下标运算符允许通过类名直接访问静态成员:
struct Config {
static std::unordered_map<std::string, int> settings;
static int operator[](const std::string& key) {
return settings[key];
}
};
std::unordered_map<std::string, int> Config::settings = {{"max_size", 100}};
int main() {
int max_size = Config["max_size"]; // 直接通过类名调用静态 operator[]
return 0;
}
1.4.2 throw() noexcept 简化
throw() 等价于 noexcept(true),简化异常规范:
void func() throw() {}
// 等价于
void func() noexcept(true)
void func2() noexcept {}
// C++11 noexcept,等价于 noexcept(true)
1.4.3 [[assume]] 属性
[[assume(condition)]] 提示编译器假设 condition 为真,优化代码(如删除冗余检查):
int div(int a, int b) {
[[assume(b != 0)]]; // 假设 b 不为 0,编译器可删除除 0 检查
return a / b;
}
二、标准库新增特性
2.1 std::expected
2.1.1 结果与错误的统一表示
std::expected<T, E> 表示操作结果(T)或错误(E),替代 std::optional+ 错误码:
#include <expected>
#include <string>
enum class Error { InvalidInput, OutOfRange };
std::expected<int, Error> parse_int(const std::string& s) {
if (s.empty())
return std::unexpected(Error::InvalidInput);
// ... 解析逻辑 ...
return 42;
}
int main() {
auto res = parse_int("42");
if (res.has_value()) {
int value = res.value(); // 获取结果
} else {
Error err = res.error(); // 获取错误
}
return 0;
}
2.1.2 and_then/or_else 链式调用
std::expected 支持链式调用,处理成功或失败的后续逻辑:
// 成功时调用 func1,失败时调用 func2
auto res = parse_int("42")
.and_then([](int x) {
return x * 2; // 成功:42 → 84
})
.or_else([](Error e) {
return std::unexpected(e); // 失败:传递错误
});
2.2 扁平化容器(flat_map/flat_set)
2.2.1 内部结构与性能对比
std::flat_map/std::flat_set 内部用 std::vector 存储键值对,排序后通过二分查找访问:
- 优点:内存连续,缓存友好,遍历速度快于
std::map。 - 缺点:插入/删除需要移动元素,性能较差。
#include <flat_map>
#include <map>
#include <chrono>
int main() {
std::flat_map<int, int> fm;
std::map<int, int> m;
// 插入性能:map 更快(O(log n) vs flat_map O(n))
// 遍历性能:flat_map 更快(缓存局部性)
return 0;
}
2.2.2 适用场景
- 遍历密集型场景:如日志分析、统计报表。
- 内存受限环境:连续存储减少内存碎片。
2.3 std::print/std::println
2.3.1 格式化输出到标准流
std::print/std::println 直接格式化输出到 stdout,简化打印代码:
#include <print>
#include <string>
int main() {
std::print("Hello, {}!\n", "World"); // 格式化输出(无自动换行)
std::println("Hello, {}!", "World"); // 自动添加换行
return 0;
}
2.3.2 与 std::format 的配合
std::print 内部使用 std::format 的格式化逻辑,支持相同的格式规范:
std::println("PI: {:.2f}", 3.14159); // 输出:PI: 3.14
2.4 ranges::to
2.4.3 范围转换为容器
ranges::to<C> 将范围转换为容器 C,简化范围到容器的转换:
#include <ranges>
#include <vector>
#include <views>
int main() {
auto view = std::views::iota(1, 6); // 生成 1-5
std::vector<int> vec = view | std::ranges::to<std::vector>(); // 转换为 vector
return 0;
}
2.5 其他标准库特性
2.5.1 std::expected
(见 2.1 节)
2.5.2 std::byteswap
字节序转换函数,支持整数类型的大小端转换:
#include <bit>
int main() {
uint16_t x = 0x1234;
uint16_t y = std::byteswap(x); // 0x3412(小端→大端)
return 0;
}
2.5.3 std::stacktrace
获取堆栈跟踪信息,辅助错误调试:
#include <stacktrace>
#include <iostream>
void func() {
auto st = std::stacktrace::current(); // 获取当前堆栈
std::cout << st << std::endl; // 打印堆栈信息
}
int main() {
func();
return 0;
}
三、C++23 特性应用场景
3.1 deducing this 简化多态设计
使用 deducing this 实现静态多态,避免 CRTP 的繁琐类型传递:
struct Drawable {
template<typename Self>
void draw(this Self&& self) {
self.draw_impl(); // 调用派生类实现
}
};
struct Circle : Drawable {
void draw_impl() {
std::cout << "Circle" << std::endl;
}
};
int main() {
Circle c;
c.draw(); // 调用 Drawable::draw → Circle::draw_impl
return 0;
}
3.2 std::expected 统一错误处理
在网络请求中返回结果或错误:
std::expected<Response, NetworkError> fetch_data(const std::string& url) {
// ... 网络请求逻辑 ...
if (success)
return Response{...};
else
return std::unexpected(NetworkError::Timeout);
}
3.3 flat_map 优化遍历性能
游戏实体组件系统中,使用 flat_map 存储组件,加快遍历速度:
std::flat_map<EntityID, TransformComponent> transforms;
std::flat_map<EntityID, RenderComponent> renders;
// 遍历所有实体组件,flat_map 缓存友好
for (auto& [id, transform] : transforms) {
auto it = renders.find(id);
if (it != renders.end()) {
render(transform, it->second);
}
}
四、编译器支持与迁移
4.1 主流编译器支持情况
- GCC:13+ 支持部分 C++23 特性(如
std::expected、flat_map)。 - Clang:16+ 支持部分特性。
- MSVC:Visual Studio 2022 17.5+ 支持部分特性。
4.2 从 C++20 迁移到 C++23
- 渐进式迁移:优先使用稳定特性(如
std::print、flat_map)。 - 兼容性:C++23 特性大多向下兼容,可混合使用 C++20 代码。
- 工具链更新:确保编译器版本支持所需特性,启用
-std=c++23编译选项。

