C++中的std::allocator

news2025/5/17 0:43:18

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_traits

template <typename T>
struct SimpleAllocator {
    using value_type = T;

    // 必须提供 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);
    }
    // 不提供 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_traits

template <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.coloradmin.cn/o/2377237.html

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

相关文章

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:

【HarmonyOS 5】鸿蒙星闪NearLink详解

【HarmonyOS 5】鸿蒙星闪NearLink详解 一、前言 鸿蒙星闪NearLink Kit 是 HarmonyOS 提供的短距离通信服务&#xff0c;支持星闪设备间的连接、数据交互。例如&#xff0c;手机可作为中心设备与外围设备&#xff08;如鼠标、手写笔、智能家电、车钥匙等&#xff09;通过星闪进…

WF24 wifi/蓝牙模块串口与手机蓝牙通信

usb-ttl ch340接线 打开串口工具SSCOM&#xff0c;端口号选择ch340接的那个口&#xff0c;波特率改成115200 DX-SMART_2.0.5.apk下载 手机打开DX-SMART软件 点击透传-搜索BLE-连接WF24-BLE 连接成功串口会收到消息 [14:37:10.591]收←◆ BLE_CONNECT_SUCCESS发送命令ATBLUFI…

通义千问席卷日本!开源界“卷王”阿里通义千问成为日本AI发展新基石

据日本经济新闻&#xff08;NIKKEI&#xff09;报道&#xff0c;通义千问已成为日本AI开发的新基础&#xff0c;其影响力正逐步扩大&#xff0c;深刻改变着日本AI产业的格局。 同时&#xff0c;日本经济新闻将通义千问Qwen2.5-Max列为全球AI模型综合评测第六名&#xff0c;不仅…

流程编辑器Bpmn与LogicFlow学习

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

Figma 新手教程学习笔记

&#x1f4fa; 视频地址&#xff1a;Figma新手教程2025&#xff5c;30分钟高效掌握Figma基础操作与UI设计流程_哔哩哔哩_bilibili &#x1f9ed; 课程结构 Figma 简介&#xff08;00:38&#xff09; 熟悉工作环境&#xff08;01:49&#xff09; 操作界面介绍&#xff08;03:…

配置Spark环境

1.上传spark安装包到某一台机器&#xff08;自己在finaShell上的机器&#xff09;。 2.解压。 把第一步上传的安装包解压到/opt/module下&#xff08;也可以自己决定解压到哪里&#xff09;。对应的命令是&#xff1a;tar -zxvf 安装包 -C /opt/module 3.重命名。进入/opt/mo…

Window下Jmeter多机压测方法

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

视频图像压缩领域中 DCT 的 DC 系数和 AC 系数详解

引言 在数字图像与视频压缩领域&#xff0c;离散余弦变换&#xff08;Discrete Cosine Transform, DCT&#xff09;凭借其卓越的能量集中特性&#xff0c;成为JPEG、MPEG等国际标准的核心技术。DCT通过将空域信号映射到频域&#xff0c;分离出DC系数&#xff08;直流分量&…

能源设备数据采集

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

Go语言安装proto并且使用gRPC服务(2025最新WINDOWS系统)

1.protobuf简介 protobuf 即 Protocol Buffers&#xff0c;是一种轻便高效的结构化数据存储格式&#xff0c;与语言、平台无关&#xff0c;可扩展可序列化。protobuf 性能和效率大幅度优于 JSON、XML 等其他的结构化数据格式。protobuf 是以二进制方式存储的&#xff0c;占用空…

[Linux性能优化] 线程卡顿优化。Linux加入USB(HID)热插拔线程占用CPU优化。Linux中CPU使用率过高优化

文章目录 [Linux性能优化] 线程卡顿优化。0、省流版本一、问题定位&#xff1a;CPU 资源分析二、线程卡顿现场复现线程优化前图片 三、线程卡顿优化方向1.如果是轮询方式2.如果是事件驱动方式 四、修改方式线程优化后图片 [Linux性能优化] 线程卡顿优化。 0、省流版本 如果采…

Ubuntu20.04下如何源码编译Carla,使用UE4源码开跑,踩坑集合

一、简介 作为一个从事算法研究的人员,无人驾驶仿真一直是比较重要的一部分,但是现在比较常见的算法验证都是在carla这个开源仿真平台上做的,所以我有二次开发carla的需求,今天就来讲讲编译CARLA。 网上的教材很多,但还是推荐大家看官网教程:Linux build - CARLA Simul…

26考研——中央处理器_数据通路的功能和基本结构(5)

408答疑 文章目录 三、数据通路的功能和基本结构数据通路的功能数据通路的组成组合逻辑元件&#xff08;操作元件&#xff09;时序逻辑元件&#xff08;状态元件&#xff09; 数据通路的基本结构CPU 内部单总线方式CPU 内部多总线方式专用数据通路方式 数据通路的操作举例通用寄…

区块链大纲笔记

中心化出现的原因是由于网络的形成&#xff08;不然就孤立了&#xff0c;这显然不符合现实&#xff0c;如&#xff0c;社会&#xff0c;计算机网路&#xff09;&#xff0c;接着由于网络中结点能力一般不对等同时为了便于管理等一系列问题&#xff0c;导致中心化网络的出现。&a…

浏览器自动化:RPA 解决方案的崛起

1. 引言 在 2025 年&#xff0c;浏览器自动化已成为企业和开发者不可或缺的工具。从网页数据抓取到自动化测试&#xff0c;这项技术不仅提高了效率&#xff0c;还推动了 Web 生态的发展。然而&#xff0c;随着浏览器指纹识别和反机器人检测的进步&#xff0c;传统的本地自动化…

手机换地方ip地址会变化吗?深入解析

在移动互联网时代&#xff0c;我们经常带着手机穿梭于不同地点&#xff0c;无论是出差旅行还是日常通勤。许多用户都好奇&#xff1a;当手机更换使用地点时&#xff0c;IP地址会随之改变吗&#xff1f;本文将深入解析手机IP地址的变化机制&#xff0c;帮助您全面了解这一常见但…

AI工具分享篇 | recraft.ai + figma 复刻技术路线图

recraft 介绍 recraft.ai 主要生成和编辑适合网站、印刷和营销的各种风格的矢量艺术、图标、3d图像和插图。其矢量化功能可将路线图转化为一个矢量图。 recraft 的注册流程非常的简单&#xff0c;邮箱注册即可&#xff0c;无需科学上网&#xff0c;3分钟就能搞定。看不懂英文…

部署安装jenkins.war(2.508)

实验目的&#xff1a;部署jenkins&#xff0c;并与gitlab关联bulid 所需软件&#xff1a;jdk-17_linux-x64_bin.tar.gz jenkins.war apache-tomcat-10.1.40.tar.gz 实验主机&#xff1a;8.10具有java环境,内存最少为4G&#xff0c;cpu双核 目录 jdk安装 …

JS手写代码篇---手写 Object.create

JS手写代码篇 在做手写题的时候&#xff0c;我们要思考两个问题 这个代码的作用是什么能够实现的效果是什么样子 1. 手写 Object.create 思路&#xff1a;创造一个对象&#xff0c;类似于Object.create()方法>将obj作为原型 // 手写 Object.create function create (ob…