C++中的std::allocator
C++中的std::allocator
文章目录
- C++中的std::allocator
- 1.`std::allocator`
- 1.1C++中的placement new 和`operator new`
- 1.2一个custom allocator的实现
- 1.3使用`std::allocator_traits`实现allocator
1.std::allocator
C++中的std::allocator
默默工作在C++STL中的所有容器的内存分配上,很多内存池是按照std::allocator
的标准来实现的,甚至很多开源的内存储项目可以和大多数STL容器兼容,在很多场景下,内存池是std::allocator
的优化。
在C++中,传统new
操作符将内存分配(operator new
,这里的operator new
是C++的内存分配原语,默认调用C语言中的malloc
,只是进行内存分配)和对象构造(构造函数)耦合。即new
运算符需要同时完成内存分配和对象构造两个操作。
std::allocator
将解耦内存分配和对象构造这两个操作,按照C++11的标准,实现一个std::allocator
需要包含以下的元素和方法:
value_type
:将模板的参数类型T
定义为value_type
,如using value_type = T;
或者typedef T value_type;
allocate()
:仅分配原始内存,功能就类似opeartor new
construct()
:在预分配的内存上构造对象(通过使用C++中的placement new机制)destroy()
:析构对象但不释放内存deallocate()
:释放原始内存(类似于operator delete
)
注释: https://cplusplus.com/reference/memory/allocator/
1.1C++中的placement new 和operator new
placement new 是C++中一种特使的内存分配的对象构造机制,它允许在已分配的内存上直接构造对象,而不是通过传统的new
操作符同时分配内存和构造对象。
placement new的语法形式为:
new (pointer) Type(constructor_arguments);
其中:
pointer
是指向已分配内存的指针Type
是要构造的对象constructor_arguments
是构造函数的参数
placement new的工作原理是,不调用operator new
来分配内存,而是在给定的内存地址上直接调用构造函数,最后返回传入的指针(将指针类型转换为目标类型)。placement new由C++标准库提供默认实现,不可重载:
// 标准库中的 placement new 声明(不可重载)
void* operator new(size_t, void* ptr) noexcept {return ptr; // 直接返回传入的指针
}
乍一看,这个placement new的实现什么都没干,是如何完成对象的构造呢?其实是依靠语法来进行创建的:
new (pointer) Type(constructor_arguments);
这里仍然调用了Type(constructor_arguments)
,即调用了对象的构造函数,在pointer
指定的内存上进行构造,举个例子:
#include <iostream>
struct Example {int value;Example(int val) : value(val) {std::cout << "Constructed at " << this << " with value " << value << std::endl;}~Example() {std::cout << "Destructed at " << this << std::endl;}
};
int main() {// 手动分配一块内存void* buffer = operator new(sizeof(Example));// 使用placement new在这块内存上构造对象Example* obj = new (buffer) Example(42);// 显式调用析构函数(这很重要!)obj->~Example();// 释放内存operator delete(buffer);return 0;
}
输出为:
Constructed at 0x7fec4c400030 with value 42
Destructed at 0x7fec4c400030
operator new
是C++的内存分配原语,默认调用malloc
进行内存分配,返回void*
,指向未初始化的原始内存,可以重载operator new
以自定义其内存分配行为:
// 自定义全局 operator new
void* operator new(size_t size){std::cout << "Allocating " << size << " bytes\n";return malloc(size);
}
使用opeartor new
和placement new的典型场景如下:
// 仅分配内存,不构造对象
void* raw_mem = operator new(sizeof(MyClass));// 需要手动构造对象(例如通过 placement new)
MyClass* obj = new (raw_mem) MyClass(); // 调用构造函数// 必须手动析构和释放
obj->~MyClass();
operator delete(raw_mem);
核心区别:
特性 | operator new | placement new |
---|---|---|
作用 | 仅分配原始内存(不构造对象) | 在已分配的内存上构造对象 |
是否调用构造函数 | 否 | 是 |
内存来源 | 通常来自于堆(可通过重载自定义) | 由程序员预先提供 |
语法 | void* p = operator new(size) | new (ptr) Type(args...) |
是否可重载 | 可重载全局或类特定的operator new | 不能重载,已经有固定实现 |
1.2一个custom allocator的实现
一个自定义的allocator
需要实现以下的方法:
方法 | 描述 | 等效操作 |
---|---|---|
allocate(n) | 分配n* sizeof(T) 字节 | operator new |
deallocate(p, n) | 释放从p 开始的n 个元素 | operator delete |
construct(p, args) | 在p 构造对象(C++17已弃用) | new(p) T(args...) |
destroy(p) | 析构p 处对象(C++17已弃用) | p->~T() |
注释:C++17 后推荐通过
std::allocator_traits
访问接口,以支持自定义分配器的可选方法。
按照C++11的标准实现一个allocator
:
#include <iostream>
#include <vector>
template<typename T>
class TrackingAllocator {
public:using value_type = T;TrackingAllocator() = default;// 支持 Rebinding(重新绑定)template<typename U>TrackingAllocator(const TrackingAllocator<U>&) {}T* allocate(size_t n) {size_t bytes = n * sizeof(T);std::cout << "Allocating " << bytes << " bytes\n";return static_cast<T*>(::operator new(bytes));}void deallocate(T* p, size_t n) {::operator delete(p);std::cout << "Deallocating " << n * sizeof(T) << " bytes\n";}// 支持同类型分配器比较(无状态)bool operator==(const TrackingAllocator&) { return true; }bool operator!=(const TrackingAllocator&) { return false; }
};// 使用示例
int main() {// 使用自定义分配器std::vector<int, TrackingAllocator<int>> vec;vec.push_back(42); // 输出分配信息vec.push_back(13); // 输出分配信息// 清空向量vec.clear(); // 输出释放信息return 0;
}
输出:
Allocating 4 bytes
Allocating 8 bytes
Deallocating 4 bytes
Deallocating 8 bytes
1.3使用std::allocator_traits
实现allocator
在 C++17 及之后版本中,推荐通过 std::allocator_traits
访问分配器接口,而非直接调用分配器的方法。这是因为 allocator_traits
提供了一种统一且安全的方式来与分配器交互,即使自定义分配器没有实现某些可选方法,也能通过默认实现正常工作。
- 兼容性:即使自定义分配器未实现某些方法(如
construct
/destroy
),allocator_traits
会提供默认实现。 - 灵活性:允许分配器仅实现必要的接口,其余由
allocator_traits
补充。 - 标准化:所有标准库容器(如
std::vector
、std::list
)内部都使用allocator_traits
而非直接调用分配器。
注释:https://cplusplus.com/reference/memory/allocator_traits/
关键接口对比(使用C++11标准 vs. C++17标准)
操作 | C++11,直接调用分配器alloc | C++17,通过allocator_traits(std::allocator_traits<Alloc>) |
---|---|---|
分配内存 | alloc.allocate(n) | allocator_traits<Alloc>::allocate(alloc, n) |
释放内存 | alloc.deallocate(p, n) | allocator_traits<Alloc>::deallocate(alloc, p, n) |
构造对象 | alloc.construct(p, args) | allocator_traits<Alloc>::construct(alloc, p, args...) |
析构对象 | alloc.destroy(p) | allocator_traits<Alloc>::destroy(alloc, p) |
获取最大大小 | alloc.max_size() | allocator_traits<Alloc>::max_size(alloc) |
重新绑定分配器类型 | alloc.rebind<U>::other | allocator_traits<Alloc>::rebind_alloc<U> |
注释:C++17 后
construct
和destroy
被废弃,推荐直接使用std::allocator_traits
或 placement new/显式析构。
举个极简分配器的例子:
#include <iostream>
#include <memory> // std::allocator_traitstemplate <typename T>
struct SimpleAllocator {using value_type = T;// 必须提供 allocate 和 deallocateT* allocate(size_t n) {return static_cast<T*>(::operator new(n * sizeof(T)));}void deallocate(T* p, size_t n) {::operator delete(p);}// 不提供 construct/destroy,由 allocator_traits 提供默认实现
};struct Widget {int id;Widget(int i) : id(i) { std::cout << "Construct Widget " << id << "\n"; }~Widget() { std::cout << "Destroy Widget " << id << "\n"; }
};int main() {using Alloc = SimpleAllocator<Widget>;Alloc alloc;// 1. 分配内存(通过 allocator_traits)auto p = std::allocator_traits<Alloc>::allocate(alloc, 1);// 2. 构造对象(即使 SimpleAllocator 没有 construct 方法!)std::allocator_traits<Alloc>::construct(alloc, p, 42); // 调用 Widget(42)// 3. 析构对象(即使 SimpleAllocator 没有 destroy 方法!)std::allocator_traits<Alloc>::destroy(alloc, p);// 4. 释放内存std::allocator_traits<Alloc>::deallocate(alloc, p, 1);return 0;
}
输出:
Construct Widget 42
Destroy Widget 42
一个更复杂的自定义分配器示例(带状态)
#include <iostream>
#include <memory> // std::allocator_traitstemplate <typename T>
class TrackingAllocator {size_t total_allocated = 0;
public:using value_type = T;T* allocate(size_t n) {total_allocated += n * sizeof(T);std::cout << "Allocated " << n * sizeof(T) << " bytes (Total: " << total_allocated << ")\n";return static_cast<T*>(::operator new(n * sizeof(T)));}void deallocate(T* p, size_t n) {total_allocated -= n * sizeof(T);std::cout << "Deallocated " << n * sizeof(T) << " bytes (Remaining: " << total_allocated << ")\n";::operator delete(p);}// 支持比较(相同类型的 TrackingAllocator 才等价)bool operator==(const TrackingAllocator& other) const {return false; // 有状态,不同实例不能混用}bool operator!=(const TrackingAllocator& other) const {return true;}
};int main() {using Alloc = TrackingAllocator<int>;Alloc alloc1, alloc2;auto p1 = std::allocator_traits<Alloc>::allocate(alloc1, 2);auto p2 = std::allocator_traits<Alloc>::allocate(alloc2, 3);// 必须用相同的 allocator 实例释放!std::allocator_traits<Alloc>::deallocate(alloc1, p1, 2);std::allocator_traits<Alloc>::deallocate(alloc2, p2, 3);return 0;
}
输出:
Allocated 8 bytes (Total: 8)
Allocated 12 bytes (Total: 12)
Deallocated 8 bytes (Remaining: 0)
Deallocated 12 bytes (Remaining: 0)
相关文章:
C++中的std::allocator
C中的std::allocator 文章目录 C中的std::allocator1.std::allocator1.1C中的placement new 和operator new1.2一个custom allocator的实现1.3使用std::allocator_traits实现allocator 1.std::allocator C中的std::allocator默默工作在CSTL中的所有容器的内存分配上࿰…...
Git/GitLab日常使用的命令指南来了!
在 GitLab 中拉取并合并代码的常见流程是通过 Git 命令来完成的。以下是一个标准的 Git 工作流,适用于从远程仓库(如 GitLab)拉取代码、切换分支、合并更新等操作。 🌐 一、基础命令:拉取最新代码 # 拉取远程仓库的所…...

aws 实践创建policy + Role
今天Cyber 通过image 来创建EC2 的时候,要添加policy, 虽然是administrator 的role, 参考Cyber 提供的link: Imageshttps://docs.cyberark.com/pam-self-hosted/14.2/en/content/pas%20cloud/images.htm#Bring 1 Step1:...
[Java实战]Spring Boot 解决跨域问题(十四)
[Java实战]Spring Boot 解决跨域问题(十四) 一、CORS 问题背景 什么是跨域问题? 当浏览器通过 JavaScript 发起跨域请求(不同协议、域名、端口)时,会触发同源策略限制,导致请求被拦截。 示例场…...

【HarmonyOS 5】鸿蒙星闪NearLink详解
【HarmonyOS 5】鸿蒙星闪NearLink详解 一、前言 鸿蒙星闪NearLink Kit 是 HarmonyOS 提供的短距离通信服务,支持星闪设备间的连接、数据交互。例如,手机可作为中心设备与外围设备(如鼠标、手写笔、智能家电、车钥匙等)通过星闪进…...
Python高级进阶:Vim与Vi使用指南
李升伟 整理 在 Python 高级进阶中,使用 Vim 或 Vi 作为代码编辑器可以显著提升开发效率,尤其是在远程服务器开发或快速脚本编辑时。以下是关于它们在 Python 开发中的高级应用详解: 1. Vim/Vi 简介 Vi:经典的 Unix 文本编辑器…...
【Python】对象生命周期全解析
Python对象生命周期全解析 在Python中,一个对象从创建到销毁会经历一系列过程,理解这些过程对于编写高效、可靠的Python代码非常重要。下面我将详细讲解Python对象的完整生命周期。 1. 对象创建阶段 (1) 内存分配 当使用类实例化时(obj MyClass())&…...
自然语言处理(NLP)在影评情感分析中的处理流程示例
自然语言处理(NLP)在影评情感分析中的处理流程示例 以影评情感分析为例,为你详细介绍自然语言处理的处理流程。在这个例子中,我们将使用 Python 和一些常用的 NLP 库,如nltk(自然语言工具包)和…...

WF24 wifi/蓝牙模块串口与手机蓝牙通信
usb-ttl ch340接线 打开串口工具SSCOM,端口号选择ch340接的那个口,波特率改成115200 DX-SMART_2.0.5.apk下载 手机打开DX-SMART软件 点击透传-搜索BLE-连接WF24-BLE 连接成功串口会收到消息 [14:37:10.591]收←◆ BLE_CONNECT_SUCCESS发送命令ATBLUFI…...
互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-3
互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-3 场景背景 面试场景设定在一家大型互联网公司,面试官为拥有10年以上经验的技术总监,专注于高并发、高可用系统的架构设计。候选人郑薪苦是一名技术潜力十足的程序员,擅…...
C++核心编程--1 内存分区模型
C程序执行时,内存可以划分为4部分 代码区:存放函数体的二进制代码 全局区:存放全局变量、静态变量、常量 栈区:局部变量、函数参数值,编译器自动分配和释放 堆区:程序员自己分配和释放 1.1 程序运行前…...
02_线性模型(回归分类模型)
用于分类的线性模型 线性模型也广泛应用于分类问题,可以利用下面的公式进行预测: $ \widehat y w[0]*x[0]w[1]*x[1]…w[p]*x[p]b > 0$ 公式看起来与线性回归的公式非常相似,但没有返回特征的加权求和,而是为预测设置了阈值…...

通义千问席卷日本!开源界“卷王”阿里通义千问成为日本AI发展新基石
据日本经济新闻(NIKKEI)报道,通义千问已成为日本AI开发的新基础,其影响力正逐步扩大,深刻改变着日本AI产业的格局。 同时,日本经济新闻将通义千问Qwen2.5-Max列为全球AI模型综合评测第六名,不仅…...

流程编辑器Bpmn与LogicFlow学习
工作流技术如何与用户交互结合(如动态表单、任务分配)处理过 XML 与 JSON 的转换自定义过 bpmn.js 的样式(如修改节点颜色、形状、图标)扩展过上下文菜单(Palette)或属性面板(Properties Panel&…...

Figma 新手教程学习笔记
📺 视频地址:Figma新手教程2025|30分钟高效掌握Figma基础操作与UI设计流程_哔哩哔哩_bilibili 🧭 课程结构 Figma 简介(00:38) 熟悉工作环境(01:49) 操作界面介绍(03:…...
RabbitMQ的工作队列模式和路由模式有什么区别?
RabbitMQ 的工作队列模式(Work Queues)和路由模式(Routing)是两种不同的消息传递模式,主要区别在于消息的分发逻辑和使用场景。以下是它们的核心差异: 1. 工作队列模式(Work Queues)…...
什么是 ANR 如何避免它
一、什么是 ANR? ANR(Application Not Responding) 是 Android 系统在应用程序主线程(UI 线程)被阻塞超过一定时间后触发的错误机制。此时系统会弹出一个对话框提示用户“应用无响应”,用户可以选择等待或强…...

配置Spark环境
1.上传spark安装包到某一台机器(自己在finaShell上的机器)。 2.解压。 把第一步上传的安装包解压到/opt/module下(也可以自己决定解压到哪里)。对应的命令是:tar -zxvf 安装包 -C /opt/module 3.重命名。进入/opt/mo…...
嵌入式硬件篇---IIC
文章目录 前言1. IC协议基础1.1 物理层特性两根信号线SCLSDA支持多主多从 标准模式电平 1.2 通信流程起始条件(Start Condition)从机地址(Slave Address)应答(ACK/NACK)数据传输:停止条件&#…...

Window下Jmeter多机压测方法
1.概述 Jmeter多机压测的原理,是通过单个jmeter客户端,控制多个远程的jmeter服务器,使他们同步的对服务器进行压力测试。 以此方式收集测试数据的好处在于: 保存测试采样数据到本地机器通过单台机器管理多个jmeter执行引擎测试…...

视频图像压缩领域中 DCT 的 DC 系数和 AC 系数详解
引言 在数字图像与视频压缩领域,离散余弦变换(Discrete Cosine Transform, DCT)凭借其卓越的能量集中特性,成为JPEG、MPEG等国际标准的核心技术。DCT通过将空域信号映射到频域,分离出DC系数(直流分量&…...
K8S cgroups详解
以下是 Kubernetes 中 cgroups(Control Groups) 的详细解析,涵盖其核心原理、在 Kubernetes 中的具体应用及实践操作: 一、cgroups 基础概念 1. 是什么? cgroups 是 Linux 内核提供的 资源隔离与控制机制,…...

能源设备数据采集
在全球可持续发展目标与环境保护理念日益深入人心的时代背景下,有效管理和优化能源使用已成为企业实现绿色转型、提升竞争力的关键路径。能源设备数据采集系统,作为能源管理的核心技术支撑,通过对各类能源生产设备运行数据的全面收集、深度分…...

Go语言安装proto并且使用gRPC服务(2025最新WINDOWS系统)
1.protobuf简介 protobuf 即 Protocol Buffers,是一种轻便高效的结构化数据存储格式,与语言、平台无关,可扩展可序列化。protobuf 性能和效率大幅度优于 JSON、XML 等其他的结构化数据格式。protobuf 是以二进制方式存储的,占用空…...

[Linux性能优化] 线程卡顿优化。Linux加入USB(HID)热插拔线程占用CPU优化。Linux中CPU使用率过高优化
文章目录 [Linux性能优化] 线程卡顿优化。0、省流版本一、问题定位:CPU 资源分析二、线程卡顿现场复现线程优化前图片 三、线程卡顿优化方向1.如果是轮询方式2.如果是事件驱动方式 四、修改方式线程优化后图片 [Linux性能优化] 线程卡顿优化。 0、省流版本 如果采…...

Ubuntu20.04下如何源码编译Carla,使用UE4源码开跑,踩坑集合
一、简介 作为一个从事算法研究的人员,无人驾驶仿真一直是比较重要的一部分,但是现在比较常见的算法验证都是在carla这个开源仿真平台上做的,所以我有二次开发carla的需求,今天就来讲讲编译CARLA。 网上的教材很多,但还是推荐大家看官网教程:Linux build - CARLA Simul…...
Java中的策略模式和模板方法模式
文章目录 1. 策略模式(Strategy Pattern)案例:支付方式选择 2. 模板方法模式(Template Method Pattern)案例:制作饮料流程 3. 策略模式 vs 模板方法模式4.总结 在Java中,策略模式和模板方法模式…...

26考研——中央处理器_数据通路的功能和基本结构(5)
408答疑 文章目录 三、数据通路的功能和基本结构数据通路的功能数据通路的组成组合逻辑元件(操作元件)时序逻辑元件(状态元件) 数据通路的基本结构CPU 内部单总线方式CPU 内部多总线方式专用数据通路方式 数据通路的操作举例通用寄…...

区块链大纲笔记
中心化出现的原因是由于网络的形成(不然就孤立了,这显然不符合现实,如,社会,计算机网路),接着由于网络中结点能力一般不对等同时为了便于管理等一系列问题,导致中心化网络的出现。&a…...
IntelliJ IDEA 集成AI编程助手全解析:从Copilot到GPT-4o Mini的实践
目录 AI编程助手的演进与核心价值GitHub Copilot深度集成指南国产新星DeepSeek配置实战GPT-4o Mini低成本接入方案三大助手对比与场景适配企业级安全与本地化部署未来发展趋势与开发者启示1. AI编程助手的演进与核心价值 1.1 技术演进图谱 #mermaid-svg-LwYPrW2Y2Pqvqgf0 {fon…...