25、【C++】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;
}

constexpr 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::expectedflat_map)。
  • Clang:16+支持部分特性。
  • MSVC:Visual Studio 2022 17.5+支持部分特性。

4.2 从C++20迁移到C++23

  • 渐进式迁移:优先使用稳定特性(如std::printflat_map)。
  • 兼容性:C++23特性大多向下兼容,可混合使用C++20代码。
  • 工具链更新:确保编译器版本支持所需特性,启用-std=c++23编译选项。

以上内容,全面覆盖了C++23的核心语言特性和标准库新增功能。C++23聚焦于提升开发效率和性能,完善C++20引入的现代特性,是C++语言持续进化的重要一步。合理应用这些特性可使代码更简洁、高效、易维护。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐