C++:allocator类(动态数组续)

1.为什么需要 allocator?

在 C++ 中,动态内存管理通常通过 new 和 delete 完成:

int* p = new int;    // 分配内存 + 构造对象
delete p;            // 析构对象 + 释放内存

但 new 和 delete 有两个问题:

  1. 耦合性:将内存分配和对象构造合并为一个操作。

  2. 灵活性不足:无法适配不同的内存管理策略(如内存池、共享内存等)。

allocator 类的核心目标就是解耦内存分配和对象构造,提供更灵活的内存管理。


 2.allocator 的核心作用

std::allocator 是标准库容器(如 vectorlistmap 等)默认使用的内存分配器。它主要有以下作用:

1. 分离内存分配和对象构造
  • 分配内存:先分配原始内存块,但不构造对象。

  • 构造对象:在已分配的内存上手动构造对象。

  • 析构对象:手动析构对象,但不释放内存。

  • 释放内存:最终释放原始内存块。

#include <memory>std::allocator<int> alloc;// 1. 分配内存(未初始化)
int* p = alloc.allocate(5); // 分配 5 个 int 的空间// 2. 构造对象
for (int i = 0; i < 5; ++i) {alloc.construct(p + i, i); // 在 p[i] 处构造 int 对象,值为 i
}// 3. 析构对象
for (int i = 0; i < 5; ++i) {alloc.destroy(p + i);      // 析构 p[i] 处的对象
}// 4. 释放内存
alloc.deallocate(p, 5);
2. 支持自定义内存管理策略

通过自定义 allocator,可以实现:

  • 内存池:预分配大块内存,减少碎片。

  • 共享内存:在进程间共享内存区域。

  • 性能优化:针对特定场景优化内存分配速度。


3.allocator 的典型使用场景

场景 1:标准库容器

所有标准库容器(如 std::vector)默认使用 std::allocator

template <class T, class Allocator = std::allocator<T>>
class vector {
private:T* data_ = nullptr;         // 指向动态数组的指针size_t size_ = 0;          // 当前元素数量size_t capacity_ = 0;      // 当前分配的内存容量Allocator allocator_;      // 内存分配器对象public:// ... 保留已有构造函数 ...// 赋值运算符(拷贝并交换 idiom)vector& operator=(const vector& other) {if (this != &other) {vector temp(other); // 利用拷贝构造函数swap(*this, temp);  // 交换资源}return *this;}// 移动赋值运算符vector& operator=(vector&& other) noexcept {if (this != &other) {clear();allocator_.deallocate(data_, capacity_);data_ = other.data_;size_ = other.size_;capacity_ = other.capacity_;allocator_ = std::move(other.allocator_);other.data_ = nullptr;other.size_ = other.capacity_ = 0;}return *this;}// 交换两个vector(noexcept保证)friend void swap(vector& a, vector& b) noexcept {using std::swap;swap(a.data_, b.data_);swap(a.size_, b.size_);swap(a.capacity_, b.capacity_);swap(a.allocator_, b.allocator_);}// 添加emplace_back支持template <class... Args>void emplace_back(Args&&... args) {if (size_ >= capacity_) {reserve(capacity_ ? capacity_ * 2 : 1);}allocator_.construct(data_ + size_++, std::forward<Args>(args)...);}// 完善reserve的异常安全void reserve(size_t new_cap) {if (new_cap <= capacity_) return;T* new_data = allocator_.allocate(new_cap);size_t i = 0;try {for (; i < size_; ++i) {allocator_.construct(new_data + i, std::move_if_noexcept(data_[i]));allocator_.destroy(data_ + i);}} catch (...) {// 回滚已构造元素for (size_t j = 0; j < i; ++j) {allocator_.destroy(new_data + j);}allocator_.deallocate(new_data, new_cap);throw;}allocator_.deallocate(data_, capacity_);data_ = new_data;capacity_ = new_cap;}// ... 保留其他已有方法 ...
};

要点说明:

1. 使用分配器进行内存管理(allocate/deallocate)
2. 实现RAII原则,在析构函数中释放资源
3. 支持基础操作:push_back/pop_back/clear
4. 包含移动语义优化性能
5. 实现迭代器访问功能
6. 包含简单的扩容策略(容量翻倍)
这个只是简单模仿vector容器的核心机制,实际标准库实现会更复杂(包含异常安全、优化策略等很多东西)

场景 2:自定义内存分配策略

例如,实现一个简单的内存池:

template <typename T>
class MemoryPoolAllocator {
public:// 必需的类型定义(C++标准要求)using value_type = T;               // 分配的元素类型using pointer = T*;                 // 指针类型using const_pointer = const T*;     // 常指针类型using size_type = std::size_t;      // 大小类型using difference_type = std::ptrdiff_t; // 指针差异类型// 分配器传播特性(影响容器拷贝行为)using propagate_on_container_copy_assignment = std::true_type;using propagate_on_container_move_assignment = std::true_type;using propagate_on_container_swap = std::true_type;/*** @brief 默认构造函数(必须支持)* @note 需要保证同类型的不同allocator实例可以互相释放内存*/MemoryPoolAllocator() noexcept = default;/*** @brief 模板拷贝构造函数(必须支持)* @tparam U 模板参数类型* @note 允许从其他模板实例化的allocator进行构造*/template <typename U>MemoryPoolAllocator(const MemoryPoolAllocator<U>&) noexcept {}/*** @brief 内存分配函数(核心接口)* @param n 需要分配的元素数量* @return 指向分配内存的指针* @exception 可能抛出std::bad_alloc或派生异常*/T* allocate(size_t n) {// TODO: 实现内存池分配逻辑// 建议方案:// 1. 计算总字节数 bytes = n * sizeof(T)// 2. 从内存池获取对齐的内存块// 3. 返回转换后的指针return static_cast<T*>(::operator new(n * sizeof(T)));}/*** @brief 内存释放函数(核心接口)* @param p 需要释放的内存指针* @param n 释放的元素数量* @note 必须保证p是通过allocate(n)分配的指针*/void deallocate(T* p, size_t n) noexcept {// TODO: 实现内存池回收逻辑// 建议方案:// 1. 将内存块标记为空闲// 2. 返回内存池供后续重用::operator delete(p);}/*** @brief 分配器比较函数(必须支持)* @note 不同实例是否应该被视为相等,需根据内存池实现决定*/bool operator==(const MemoryPoolAllocator&) const noexcept { return true; // 假设所有实例使用同一内存池}bool operator!=(const MemoryPoolAllocator&) const noexcept {return false;}/*** @brief 可选:对象构造函数(C++20前需要)* @tparam Args 构造参数类型*/template <typename... Args>void construct(T* p, Args&&... args) {::new(static_cast<void*>(p)) T(std::forward<Args>(args)...);}/*** @brief 可选:对象析构函数(C++20前需要)*/void destroy(T* p) {p->~T();}
};
场景 3:避免默认初始化

默认的 new 会调用构造函数,而 allocator 可以先分配内存,再按需构造对象:

std::allocator<std::string> alloc;
std::string* p = alloc.allocate(3); // 仅分配内存,不构造对象// 按需构造
alloc.construct(p, "hello");         // 构造第一个 string
alloc.construct(p + 1, "world");     // 构造第二个 string

4.allocator 的关键接口

以下是 std::allocator 的核心方法:

方法作用
allocate(n)分配 n 个对象的原始内存(未初始化)
deallocate(p, n)释放内存(需先析构所有对象)
construct(p, args)在位置 p 构造对象,参数为 args
destroy(p)析构 p 处的对象

5.自定义 allocator 的要点

5.1. 必须提供的类型别名

自定义 allocator 需要定义以下类型:

template <typename T>
class CustomAllocator {
public:using value_type = T; // 必须定义// 其他必要类型...
};
5.2. 实现必要接口

至少需要实现 allocate 和 deallocate 方法:

T* allocate(size_t n) {return static_cast<T*>(::operator new(n * sizeof(T)));
}void deallocate(T* p, size_t n) {::operator delete(p);
}
5.3. 支持 rebind 机制

容器可能需要分配其他类型的对象(如链表节点的分配器):

template <typename U>
struct rebind {using other = CustomAllocator<U>;
};

6.C++17 后的改进

C++17 引入了 std::allocator_traits,简化了自定义 allocator 的实现。即使自定义分配器未实现某些接口,allocator_traits 会提供默认实现:

template <typename Alloc>
using allocator_traits = std::allocator_traits<Alloc>;// 使用示例
auto p = allocator_traits<Alloc>::allocate(alloc, n);

7.总结

  • 核心作用:解耦内存分配与对象构造,提供更灵活的内存管理。

  • 默认行为:标准库容器使用 std::allocator

  • 自定义场景:内存池、性能优化、特殊内存区域(如共享内存)。

  • 关键接口allocatedeallocateconstructdestroy

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/74986.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

北斗导航 | 中国北斗卫星导航系统的发展历程——“三步走”战略:背景,信号频点,调制方式,短报文,等

中国北斗卫星导航系统的发展历程按照“三步走”战略逐步推进,从区域服务到全球覆盖,形成了北斗一号、北斗二号、北斗三号三代系统的迭代升级,展现了中国航天科技的自主创新与突破。以下是各阶段的核心内容与发展特点综述:一、北斗一号:中国卫星导航的奠基(1994-2003年) …

Headless Chrome 优化:减少内存占用与提速技巧

在当今数据驱动的时代&#xff0c;爬虫技术在各行各业扮演着重要角色。传统的爬虫方法往往因为界面渲染和资源消耗过高而无法满足大规模数据采集的需求。本文将深度剖析 Headless Chrome 的优化方案&#xff0c;重点探讨如何利用代理 IP、Cookie 和 User-Agent 设置实现内存占用…

英伟达GB300新宠:新型LPDDR5X SOCAMM内存

随着人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;和高性能计算&#xff08;HPC&#xff09;应用的快速发展&#xff0c;对于高效能、大容量且低延迟内存的需求日益增长。NVIDIA在其GB系列GPU中引入了不同的内存模块设计&#xff0c;以满足这些严格…

静态网页应用开发环境搭建实战教程

1. 前言 静态网页开发是前端工程师的基础技能之一&#xff0c;无论是个人博客、企业官网还是简单的Web应用&#xff0c;都离不开HTML、CSS和JavaScript。搭建一个高效的开发环境&#xff0c;能够极大提升开发效率&#xff0c;减少重复工作&#xff0c;并优化调试体验。 本教程…

Python每日一题(9)

Python每日一题 2025.3.29 一、题目二、分析三、源代码四、deepseek答案五、源代码与ai分析 一、题目 question["""企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10%,利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部…

游戏引擎学习第187天

看起来观众解决了上次的bug 昨天遇到了一个相对困难的bug&#xff0c;可以说它相当棘手。刚开始的时候&#xff0c;没有立刻想到什么合适的解决办法&#xff0c;所以今天得从头开始&#xff0c;逐步验证之前的假设&#xff0c;收集足够的信息&#xff0c;逐一排查可能的原因&a…

【入门初级篇】布局类组件的使用(1)

【入门初级篇】布局类组件的使用&#xff08;1&#xff09; 视频要点 &#xff08;1&#xff09;章节大纲介绍 &#xff08;2&#xff09;布局类组件类型介绍&#xff1a;行布局、列布局、标题 &#xff08;3&#xff09;实操演示&#xff1a;列表统计查询布局模型 点击访问my…

对内核fork进程中写时复制的理解记录

前言 文章写于学习Redis时对aof后台重写中写时复制的疑问 一、感到不理解的歧义 在部分技术文档中&#xff08;以小林的文章为例&#xff09;&#xff0c;对写时复制后的内存权限存在如歧义&#xff1a; ! 二、正确技术表述 根据Linux内核实现&#xff08;5.15版本&#x…

Ditto-Talkinghead:阿里巴巴数字人技术新突破 [特殊字符]️

Ditto-Talkinghead&#xff1a;阿里巴巴数字人技术新突破 &#x1f5e3;️ 阿里巴巴推出了一项新的数字人技术&#xff0c;名为 Ditto-Talkinghead。这项技术主要用于生成由音频驱动的说话头&#xff0c;也就是我们常说的“数字人”。不过&#xff0c;现有的基于扩散模型的同类…

.NET开发基础知识1-10

1. 依赖注入&#xff08;Dependency Injection&#xff09; 技术知识&#xff1a;依赖注入是一种设计模式&#xff0c;它允许将对象的依赖关系从对象本身中分离出来&#xff0c;通过构造函数、属性或方法参数等方式注入到对象中。这样可以提高代码的可测试性、可维护性和可扩展…

每日一题 MySQL基础知识----(三)

数据库常用基础知识&#xff1a;代码讲解和实验 1.创建数据库student 02&#xff0c;创建一个名为student02的数据库 CREATE DATABASE student02; 2.在student02中创建一张 students表&#xff0c;并且具有学生的编号id&#xff0c;姓名name&#xff0c;年龄age&#xff0c;生…

MySQL多表查询实验

1.数据准备 -- 以下语句用于创建 students 表&#xff0c;该表存储学生的基本信息 -- 定义表名为 students CREATE TABLE students (-- 定义学生的唯一标识符&#xff0c;类型为整数&#xff0c;作为主键&#xff0c;且支持自动递增student_id INT PRIMARY KEY AUTO_INCREMENT…

windows第二十章 单文档应用程序

文章目录 单文档定义新建一个单文档应用程序单文档应用程序组成&#xff1a;APP应用程序类框架类&#xff08;窗口类&#xff09;视图类&#xff08;窗口类&#xff0c;属于框架的子窗口&#xff09;文档类&#xff08;对数据进行保存读取操作&#xff09; 直接用向导创建单文档…

C++ 初阶总复习 (16~30)

C 初阶总复习 &#xff08;16~30&#xff09; 目的16. 2009. volatile关键字的作用17. 2010.什么是多态 简单介绍下C的多态18. 2011. 什么是虚函数 介绍下C中虚函数的原理19. 2012 构造函数可以是虚函数嘛20. 2013.析构函数一定要是虚函数嘛&#xff1f;21. 2015. 什么是C中的虚…

第一天 Linux驱动程序简介

目录 一、驱动的作用 二、裸机驱动 VS linux驱动 1、裸机驱动 2、linux驱动 三、linux驱动位于哪里&#xff1f; 四、应用编程 VS 内核编程 1、共同点 2、不同点 五、linux驱动分类 1、字符设备 2、块设备 3、网络设备 六、Linux驱动学习难点与误区 1、学习难点 …

PaddleX产线集成功能的使用整理

一、环境搭建 1.1 安装paddle-gpu 需要根据安装机器的cuda的版本&#xff0c;选择合适的版本进行安装 #安装paddle-gpu 官网链接 https://www.paddlepaddle.org.cn/install/quick?docurl/documentation/docs/zh/install/pip/linux-pip.html python -m pip install paddle…

docker-compese 启动mysql8.0.36与phpmyadmin,并使用web连接数据库

1、找一个文件夹&#xff0c;比如 E:\zqy\file\mysql&#xff0c;cd到这个目录下创建文件docker-compose.yml 2、将下面的代码块复制到docker-compose.yml文件中 version: 3.3 services:mysql:image: mysql:8.0.36container_name: mysqlrestart: alwaysports:- 3306:3306netw…

解决 Gradle 构建错误:Could not get unknown property ‘withoutJclOverSlf4J’

解决 Gradle 构建错误&#xff1a;Could not get unknown property ‘withoutJclOverSlf4J’ 在构建 Spring 源码或其他基于 Gradle 的项目时&#xff0c;可能会遇到如下错误&#xff1a; Could not get unknown property withoutJclOverSlf4J for object of type org.gradle…

mcp 接freecad画齿轮

from mcp.server.fastmcp import FastMCP import freecad.gears.commands import os from freecad import app from freecad import part mcp FastMCP("Demo")mcp.tool() def create_gear(num_teeth20,height10,double_helix True):"""创建一个渐开线…

【大前端系列19】JavaScript核心:Promise异步编程与async/await实践

JavaScript核心&#xff1a;Promise异步编程与async/await实践 系列: 「全栈进化&#xff1a;大前端开发完全指南」系列第19篇 核心: 深入理解Promise机制与async/await语法&#xff0c;掌握现代异步编程技术 &#x1f4cc; 引言 在JavaScript的世界中&#xff0c;异步编程是无…