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 newplacement 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::vectorstd::list)内部都使用 allocator_traits 而非直接调用分配器。

注释:https://cplusplus.com/reference/memory/allocator_traits/

关键接口对比(使用C++11标准 vs. C++17标准)

操作C++11,直接调用分配器allocC++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>::otherallocator_traits<Alloc>::rebind_alloc<U>

注释:C++17 后 constructdestroy 被废弃,推荐直接使用 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)

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

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

相关文章

CodeBuddy编程新范式

不会写&#xff1f;不想写&#xff1f; 腾讯推出的CodeBuddy彻底解放双手。 示例 以下是我对CodeBuddy的一个小体验。 我只用一行文字对CodeBuddy说明了一下我的需求&#xff0c;剩下的全部就交给了CodeBuddy&#xff0c;我需要做的就是验收结果即可。 1.首先CodeBuddy会对任…

QML学习01(设置宽度、高度、坐标点、标题,信号与槽,键盘事件)

QML学习 1、前言2、QML3、QML和QWidget的区别3、QtQuick下的Windows应用4、总结 1、前言 记录一下QML学习的过程&#xff0c;方便自己日后回顾&#xff0c;也可以给有需要的人提供帮助。 2、QML QML是 Qt 框架中的一种声明式编程语言&#xff0c;专门用于快速设计和开发用户…

在VSCode中接入DeepSeek的指南

本文将介绍三种主流接入方式,涵盖本地模型调用和云端API接入方案。 一、环境准备 1.1 基础要求 VSCode 1.80+Node.js 16.x+Python 3.8+(本地部署场景)已部署的DeepSeek服务(本地或云端)1.2 安装必备插件 # 打开VSCode插件面板(Ctrl+Shift+X) 搜索并安装: - DeepSeek Of…

机器学习-计量经济学

机器学习 不要事前决定变量关系&#xff0c;关键是谁也不知道啊&#xff0c;机器学习学习的模型&#xff08;那也不是真实的关系啊&#xff09; 这就是自然学科的好处&#xff1a;只要不断的优化这个未知的东西&#xff08;函数&#xff09;&#xff0c;然后在数据上&#xff…

五、Linux账号与权限管理

1、管理用户和组账号 1.1、用户 1.1.1、用户的概念及作用 在Linux系统中,用户(User)指的是可以访问系统资源的个体实体。每个用户都有一个唯一的用户账号,用于标识和管理其在系统中的活动和访问权限。 用户的重要性和功能: 身份认证和访问控制: 用户账号用于身份认证,确…

精益数据分析(61/126):移情阶段评分体系构建与实战案例解析

精益数据分析&#xff08;61/126&#xff09;&#xff1a;移情阶段评分体系构建与实战案例解析 在创业的移情阶段&#xff0c;如何科学评估用户需求的真实性与紧迫性&#xff0c;是决定后续产品方向的关键。今天&#xff0c;我们结合《精益数据分析》中的评分框架&#xff0c;…

完成反射宇宙的最后一块拼图:泛型集合

反射,c#的黑科技,一手打造漂亮的,专属于自己的属性框 之前分享的: 如何写一个自定义属性控件的功能,但是只是对基础的类型,比如String,bool,int等,但是对list<T>,Vector<T>这种泛型集合类型支持的不是很好,刚好最近重新研究了一下,将这个非常重要的功能完成了. 效…

Redis--基础知识点--26--过期删除策略 与 淘汰策略

Redis 的过期策略和淘汰策略是内存管理的核心机制&#xff0c;分别用于处理键的自动失效和内存不足时的数据清理。以下是详细说明&#xff1a; 1 、过期删除策略&#xff08;Expiration Policy&#xff09; 处理已设置过期时间&#xff08;EXPIRE&#xff09;的键&#xff0c;…

第六天——贪心算法——字符串分隔

1. 题目 给定一个字符串 s&#xff0c;我们需要将其划分为尽可能多的部分&#xff0c;使得同一字母最多出现在一个部分中。 例如&#xff1a;字符串 "ababcc" 可以划分为 ["abab", "cc"]&#xff0c;但要避免 ["aba", "bcc&quo…

[原创](现代Delphi 12指南):[macOS 64bit App开发]: 注意“回车换行“的跨平台使用.

[作者] 常用网名: 猪头三 出生日期: 1981.XX.XX 企鹅交流: 643439947 个人网站: 80x86汇编小站 编程生涯: 2001年~至今[共24年] 职业生涯: 22年 开发语言: C/C++、80x86ASM、Object Pascal、Objective-C、C#、R、Python、PHP、Perl、 开发工具: Visual Studio、Delphi、XCode、…

Maven 插件参数注入与Mojo开发详解

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家&#xff0c;历代文学网&#xff08;PC端可以访问&#xff1a;https://literature.sinhy.com/#/?__c1000&#xff0c;移动端可微信小程序搜索“历代文学”&#xff09;总架构师&#xff0c;15年工作经验&#xff0c;精通Java编…

扩增子分析|R分析之微生物生态网络稳定性评估之节点和连接的恒常性、节点持久性以及组成稳定性指数计算

一、引言 周集中老师团队于2021年在Nature climate change发表的文章&#xff0c;阐述了网络稳定性评估的原理算法&#xff0c;并提供了完整的代码。自此对微生物生态网络的评估具有更全面的指标&#xff0c;自此网络稳定性的评估广受大家欢迎。本文将介绍网络稳定性之节点和连…

人体肢体渲染-一步几个脚印从头设计数字生命——仙盟创梦IDE

人体肢体动作数据集-太极拳 渲染代码 # 初始化Pygame pygame.init()# 设置窗口尺寸 WINDOW_WIDTH 800 WINDOW_HEIGHT 600 window pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT)) pygame.display.set_caption("动作回放")# 设置帧率 FPS 30 clock pyg…

强化学习入门:马尔科夫奖励过程

文章目录 前言1、组成部分2、应用例子3、马尔科夫奖励过程总结 前言 最近想开一个关于强化学习专栏&#xff0c;因为DeepSeek-R1很火&#xff0c;但本人对于LLM连门都没入。因此&#xff0c;只是记录一些类似的读书笔记&#xff0c;内容不深&#xff0c;大多数只是一些概念的东…

腾讯开源实时语音大模型VITA-audio,92mstoken极速响应,支持多语言~

简介 VITA-Audio 是一个由腾讯优图实验室&#xff08;Tencent Youtu Lab&#xff09;、南京大学和厦门大学的研究人员共同开发的项目&#xff0c;旨在解决现有语音模型在流式生成&#xff08;streaming&#xff09;场景下生成第一个音频令牌&#xff08;token&#xff09;时的高…

测序的原理

Sanger 测序原理 https://v.qq.com/x/page/d0124c0k44t.html illumina 测序原理&#xff1a; https://v.qq.com/x/page/i0770fd7r9i.html PacBio 第三代 SMRT 单分子测序 https://v.qq.com/x/page/r03534cry7u.html Ion torrent 测序原理 https://v.qq.com/x/page/v01754s6r82.…

高项-逻辑数据模型

逻辑数据模型的核心理解 1. 定义与特点 逻辑数据模型&#xff08;Logical Data Model, LDM&#xff09;&#xff1a; 是一种抽象的数据结构设计&#xff0c;用于描述业务实体&#xff08;如客户、订单&#xff09;及其关系&#xff08;如“客户下单”&#xff09;&#xff0c…

《数字分身进化论:React Native与Flutter如何打造沉浸式虚拟形象编辑》

React Native&#xff0c;依托JavaScript语言&#xff0c;借助其成熟的React生态系统&#xff0c;开发者能够快速上手&#xff0c;将前端开发的经验巧妙运用到移动应用开发中。它通过JavaScript桥接机制调用原生组件&#xff0c;实现与iOS和Android系统的深度交互&#xff0c;这…

提高绳牵引并联连续体机器人运动学建模精度的基于Transformer的分段学习方法

合肥工业大学王正雨老师团队针对绳牵引并联连续体机器人的运动学建模提出一种基于Transformer网络的分段学习方法&#xff0c;该方法较传统建模性能卓越、精度更高。相关研究论文“Transformer-based segmented learning for kinematics modelling of a cable-driven parallel …

【PX4飞控】在 Matlab Simulink 中使用 Mavlink 协议与 PX4 飞行器进行交互

这里列举一些从官网收集的比较有趣或者实用的功能。 编写 m 脚本与飞行器建立 UDP 连接&#xff0c;并实时可视化 Mavlink 消息内容&#xff0c;或者读取脚本离线分析数据。不光能显示 GPS 位置或者姿态等信息的时间曲线&#xff0c;可以利用 Matlab Plot 功能快速定制化显示一…