C++ 中的多态分配器

2025 年 5 月 13 日 | 阅读 10 分钟

引言

在内存分配方面,C++ 语言一直允许用户定义自己的分配器,这些分配器负责内存的分配、释放和重用。这些分配器与类型绑定;使用分配器的每个容器或类都必须包含它。从设计角度来看,在某些内存管理策略可以互换或与容器实现完全分离的情况下,这种情况不太好。

为了解决其中一些问题,C++17 将多态资源和各种多态分配器的概念引入了语言,从而实现了一种高效、灵活的运行时内存分配管理机制。因为多态分配器将内存资源与分配器分离开来,所以它们是上下文敏感的,并且可以动态分配,这使得它们适用于当今的软件工程。

问题陈述

让我们设想一个应用程序可能提供许多不同的内存分配场景的情况。例如,在

  • 实时系统: 需要大量内存,并且分配应始终高效且可预测地执行。
  • 性能敏感的应用程序可以使用为适应特定分配和释放模式而设计的某些分配器。
  • 共享内存上下文: 为跨越共享内存池中多个容器的分配器创建一种印象。

在这些情况下应用传统分配器可能导致

  • 代码重复和冗长。
  • 完全丧失了交换分配策略的能力。
  • 采用通用的内存管理技术导致性能下降。

在这些情况下,需要多态分配器来更复杂地解决问题。

什么是多态分配器?

在 C++ 中,多态分配器是 `<memory_resource>` 库的组件,与内存资源 (`std::pmr::memory_resource`) 配合使用。主要概念是消除分配器与内存资源之间的连接:应该在执行时完成在编译时执行的操作。

关键组件

1. 内存资源 (`std::pmr::memory_resource`)

抽象类代表灵活的内存分配策略。其中一些是

  • `std::pmr::new_delete_resource`: 此资源使用全局的 `new` 和 `delete`。
  • `std::pmr::monotonic_buffer_resource`: 此类型的资源将在有限的缓冲区中分配,并且更适合于已知分配大小且生命周期较短的情况。

2. 多态分配器 (`std::pmr::polymorphic_allocator`)

  • 它只是在 `memory_resource` 之上增加了一些功能。它可以与标准容器结合使用,以在程序运行时根据需要更改使用的内存分配策略。

3. 支持 PMR 的标准容器

  • 许多 STL 容器是多态变体,例如 `std::pmr::vector` 或 `std::pmr::string`。

它是如何工作的?

1. 基本设置

  • 要使用多态分配器
  • 创建或使用现有的内存资源。
  • 将资源传递给多态分配器。
  • 将分配器与容器一起使用。

2. 切换内存资源

主要好处之一是能够动态切换内存资源

程序 1:带有多态分配器的任务队列

输出

 
Executing task: Task A
High-priority Task A   

说明

  1. 内存资源
    • `std::pmr::monotonic_buffer_resource` 用于短期、高优先级任务。
    • `std::pmr::unsynchronized_pool_resource` 用于通用、低优先级任务。
  2. 任务队列
    • 使用优先级队列来管理任务,自定义比较器决定执行顺序。
    • 任务使用与特定内存资源关联的多态分配器进行分配。
  3. 线程安全执行
    • TaskQueue 类使用 `std::mutex` 和 `std::condition_variable` 来确保线程安全。
    • 多个线程并发处理任务,展示了多态分配器在多线程环境中的灵活性。
  4. 动态资源切换
    • 分配器可以在不修改容器逻辑的情况下轻松切换内存资源。

程序 2:带有多态分配器的场景图示例

输出

 
- Root
  - Camera
    - Camera Settings
  - Light
    - Intensity
    - Color
  - Mesh
    - Material
    - Vertices
    - Normals   

说明

1. SceneNode 类

  • 表示场景图中的一个节点。
  • 存储
    • name: 节点的名称(例如,“Root”、“Camera”)。
    • children: 子节点列表(`std::vector`),表示层次结构。
  • 功能:
    • addChild: 将子节点添加到当前节点并返回其引用。
    • printGraph: 递归打印节点及其子节点,格式化以反映层次结构。

2. Main 函数

  • 创建一个根节点(“Root”)作为场景图的起点。
  • 添加子节点以表示简单的场景结构
    • 一个带有“Camera Settings”子节点的 **Camera** 节点。
    • 一个带有“Intensity”和“Color”子节点的 **Light** 节点。
    • 一个带有“Material”、“Vertices”和“Normals”子节点的 **Mesh** 节点。
  • 调用 `printGraph` 来显示层次结构。

程序 3

输出

 
Simulation step 1:
Intersection Downtown Intersection has 2 connected roads.
Intersection Uptown Intersection has 2 connected roads.
Road Main St has 1 vehicles.
Vehicle Car 1 is moving to Downtown Intersection at speed 60 km/h.
Road 2nd Ave has 1 vehicles.
Vehicle Car 2 is moving to Uptown Intersection at speed 45 km/h.
Vehicle Car 1 is moving to Uptown Intersection at speed 60 km/h.
Vehicle Car 2 is moving to Downtown Intersection at speed 45 km/h.
=======================
Simulation step 2:
Intersection Downtown Intersection has 2 connected roads.
Intersection Uptown Intersection has 2 connected roads.
Road Main St has 1 vehicles.
Vehicle Car 1 has no more routes.
Road 2nd Ave has 1 vehicles.
Vehicle Car 2 has no more routes.
Vehicle Car 1 has no more routes.
Vehicle Car 2 has no more routes.
=======================
Simulation step 3:
Intersection Downtown Intersection has 2 connected roads.
Intersection Uptown Intersection has 2 connected roads.
Road Main St has 1 vehicles.
Vehicle Car 1 has no more routes.
Road 2nd Ave has 1 vehicles.
Vehicle Car 2 has no more routes.
Vehicle Car 1 has no more routes.
Vehicle Car 2 has no more routes.
=======================
Simulation step 4:
Intersection Downtown Intersection has 2 connected roads.
Intersection Uptown Intersection has 2 connected roads.
Road Main St has 1 vehicles.
Vehicle Car 1 has no more routes.
Road 2nd Ave has 1 vehicles.
Vehicle Car 2 has no more routes.
Vehicle Car 1 has no more routes.
Vehicle Car 2 has no more routes.
=======================
Simulation step 5:
Intersection Downtown Intersection has 2 connected roads.
Intersection Uptown Intersection has 2 connected roads.
Road Main St has 1 vehicles.
Vehicle Car 1 has no more routes.
Road 2nd Ave has 1 vehicles.
Vehicle Car 2 has no more routes.
Vehicle Car 1 has no more routes.
Vehicle Car 2 has no more routes.
=======================   

说明

  1. 实体和多态分配器
    • Intersection、Road 和 Vehicle 使用 `std::pmr::vector` 来管理动态数据,例如连接的道路或车辆路线。
    • TrafficSimulation 类使用多态分配器来高效管理所有实体。
  2. 内存资源
    • 使用 `std::pmr::monotonic_buffer_resource` 作为底层内存资源,在模拟期间最小化分配开销。
  3. 动态模拟
    • 模拟分多步进行,每个实体更新其状态。车辆沿其路线移动,道路报告其包含的车辆,交叉路口显示其连接的道路。
  4. 可扩展性
    • 多态分配器和单调内存资源可以高效地处理大量动态实体,避免频繁的堆分配。

使用多态分配器的好处

  • 灵活性: 消除任何额外的约束或担忧,并能够将使用的内存分配策略与容器内部隔离开。
  • 效率: 它允许使用特定区域(如池或单调分配器)中更有效的内存资源。
  • 代码可重用性: 内存管理逻辑、模式甚至想法可以在不同的容器和应用程序中重新定义。
  • 互操作性: 多态容器与分配器能够更快、更容易地使用内存资源。

用途

  • 游戏开发: 对于具有特定生命周期的实体,是一种很好的内存节省方法。
  • 金融系统: 流行的 HFT(高频交易)转换有多个集成策略;一种实现利用了通用内存池。
  • 嵌入式系统: 可以在整体硬件约束内为嵌入式系统设计自定义分配器。

结论

总之,C++ 多态分配器开启了现代内存分配模式的新趋势——可能是允许程序员更高效、更易于管理地构建基于应用程序的系统的最关键属性。多态分配器将能够创建使用多样化、变化和异构内存管理策略的应用程序,并推动应用程序的开发。

碎片化的对立面是一致性,它允许提高性能、减少碎片和内存资源的重用;多态分配器为实现这些目标提供了最简单、最优雅的解决方案。