当前位置: 首页 > article >正文

详解加实操C++之分配器


在 C++ 编程领域,分配器(Allocator)是一项极为关键的特性,它构建了一套灵活且强大的机制,专门用于抽象化和精细化管理内存的分配与释放过程。分配器在标准模板库(STL)容器中扮演着核心角色,是 STL 高效运行的重要支撑。
分配器的设计理念具有深远的意义,它赋予了开发者高度的自主性,允许开发者根据具体的应用场景和需求,定制个性化的内存管理策略。这种定制能力带来了诸多显著的优势,比如在 性能优化方面,开发者可以根据程序的内存使用模式,设计出更高效的内存分配和回收方案,减少内存碎片,提高内存访问速度,从而显著提升程序的整体性能 。对于一些有特殊需求的场景,如实时系统、嵌入式系统等,分配器可以确保内存的稳定供应和高效利用,满足系统对内存的严格要求。此外,通过分配器还能实现一些特定的功能,像内存池技术,它可以预先分配一大块内存,后续的内存分配和释放操作都在这块内存池中进行,减少了系统调用的开销;还有共享内存的使用,通过分配器可以方便地实现多个进程或线程之间的内存共享,提高数据交换的效率.


1 分配器的概念

基本概念

分配器本质上是一个模板类,它为容器(如 std::vector、std::list 等)提供了内存管理的接口。通过使用分配器,容器无需关心具体的内存分配和释放细节,只需调用分配器提供的接口来完成这些操作。这样做的好处是将内存管理的逻辑与容器的使用分离开来,使得容器可以更加灵活地适应不同的内存管理策略。

核心功能
  • 内存分配:为对象分配原始的未构造的内存。
  • 对象构造:在已分配的内存上构造对象。
  • 对象销毁:调用对象的析构函数,销毁对象。
  • 内存释放:释放先前分配的内存。
核心接口
  • allocate 函数:用于分配指定数量的对象所需的内存空间。它接受一个表示对象数量的参数,并返回一个指向分配内存起始位置的指针。例如:
    pointer allocate(size_type n, const void* hint = 0); 其中,n 是要分配的对象数量,hint 是一个可选的指针,用于提供分配内存的建议位置
  • deallocate 函数:用于释放之前通过 allocate 函数分配的内存空间。它接受一个指向要释放内存的指针和之前分配的对象数量作为参数。例如:
    void deallocate(pointer p, size_type n);
  • allocator_traits 是一个模板类,用于提供分配器的统一接口,它本身并不直接接受调用时的参数,而是在模板实例化时需要指定分配器类型
#include <memory>
#include <vector>

template<typename T>
class MyAllocator {
    // 自定义分配器的具体实现
    using value_type = T;
    template< class Alloc, class T, class... Args >
static void construct( Alloc& a, T* p, Args&&... args );

template< class Alloc, class T >
static void destroy( Alloc& a, T* p );
};

// 实例化 std::allocator_traits 模板类 
using MyAllocatorTraits = std::allocator_traits<MyAllocator<int>>;
//表明该分配器将用于管理 int 类型对象的内存

construct 函数:
a:类型为 Alloc&,是分配器对象的引用。通过这个引用,construct 函数可以调用分配器中可能存在的构造对象的方法。
p:类型为 T*,是指向未构造内存的指针。construct 函数将在这块内存上构造一个 T 类型的对象。
args…:这是一个可变参数包,用于传递构造 T 类型对象所需的参数。这些参数会被转发给 T 的构造函数

deconstruct 函数 a:同样是分配器对象的引用,用于调用分配器中可能存在的销毁对象的方法。
p:指向已构造对象的指针,destroy 函数会调用该对象的析构函数来销毁它。

  • uninitialized_fill

template< class ForwardIt, class T > void uninitialized_fill( ForwardIt first, ForwardIt last, const T& value );

first:类型为 ForwardIt,是一个前向迭代器,指向要填充的未初始化内存区域的起始位置。
last:同样是 ForwardIt 类型的前向迭代器,指向要填充的未初始化内存区域的结束位置(该位置不包含在填充范围内)。
value:类型为 const T&,是一个常量引用,代表要填充到内存区域中的值。std::uninitialized_fill 会在 [first, last) 范围内的每个位置构造一个 T 类型的对象,并且用 value 来初始化这些对象

分配器与容器

在 C++ 里,不进行自定义分配器操作,直接使用标准库中的 std::allocator 来搭配容器是很常见且简单的做法。std::allocator 是标准库提供的默认分配器,它能为容器提供基本的内存分配和释放功能。下面为你展示使用 std::allocator 和不同容器的示例。

#include <vector>
#include <iostream>

// 假设已经定义了 PoolAllocator<T>

int main() {
    std::vector<int, PoolAllocator<int>> vec;
    vec.reserve(100); // 预留空间

    for (int i = 0; i < 100; ++i) {
        vec.push_back(i);
    }

    for (const auto& val : vec) {
        std::cout << val << " ";
    }

    return 0;
}

自定义

使用自定义

#include <iostream>
#include <memory>
#include <vector>

// 自定义分配器模板类
template <typename T>
class MyAllocator {
public:
    // 定义分配器的类型别名
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;
    using difference_type = std::ptrdiff_t;

    // 用于分配内存的函数
    pointer allocate(size_type n) {
        if (n > std::numeric_limits<size_type>::max() / sizeof(T)) {
            throw std::bad_alloc();
        }
        auto p = static_cast<pointer>(std::malloc(n * sizeof(T)));
        if (!p) {
            throw std::bad_alloc();
        }
        return p;
    }

    // 用于释放内存的函数
    void deallocate(pointer p, size_type) {
        std::free(p);
    }

    // 构造对象的函数
    template <typename U, typename... Args>
    void construct(U* p, Args&&... args) {
        ::new((void*)p) U(std::forward<Args>(args)...);
    }

    // 销毁对象的函数
    template <typename U>
    void destroy(U* p) {
        p->~U();
    }
};

// 特化 std::allocator_traits 以支持自定义分配器
template <typename T, typename U>
struct std::allocator_traits<MyAllocator<T>> {
    using allocator_type = MyAllocator<T>;
    using value_type = typename allocator_type::value_type;
    using pointer = typename allocator_type::pointer;
    using const_pointer = typename allocator_type::const_pointer;
    using reference = typename allocator_type::reference;
    using const_reference = typename allocator_type::const_reference;
    using size_type = typename allocator_type::size_type;
    using difference_type = typename allocator_type::difference_type;

    static pointer allocate(allocator_type& a, size_type n) {
        return a.allocate(n);
    }

    static void deallocate(allocator_type& a, pointer p, size_type n) {
        a.deallocate(p, n);
    }

    template <typename... Args>
    static void construct(allocator_type& a, pointer p, Args&&... args) {
        a.construct(p, std::forward<Args>(args)...);
    }

    template <typename U>
    static void destroy(allocator_type& a, U* p) {
        a.destroy(p);
    }
};

int main() {
    // 使用自定义分配器创建 std::vector 容器
    std::vector<int, MyAllocator<int>> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
    }

    // 输出容器中的元素
    for (int num : vec) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}
    


http://www.kler.cn/a/593961.html

相关文章:

  • 【QT】系统事件入门 -- 文件 QFile基础和示例
  • 介绍一下TiDB、RocksDb、levelDB、LSM 树、SSTable。
  • ai数字人系统功能详细代码
  • 算法|2025最强优化算法
  • 现代复古像素风品牌海报游戏排版设计装饰英文字体 Psygen — Modern Pixel Font
  • Java 中 CopyOnWriteArrayList 的底层数据结构及相关分析
  • 力扣刷题——25.K个一组翻转链表
  • 深拷贝在 JavaScript 中的几种实现方式对比
  • xss-labs靶场训练
  • 调和Django与Sql server2019的关系
  • 【leetcode hot 100 208】实现Trie(前缀树)
  • HAl库开发中断方式接收Can报文的详细流程
  • 使用AI一步一步实现若依前端(15)
  • 体检管理页面开发:问题总结与思考
  • Kali Linux更改国内镜像源
  • 传输层协议 — TCP协议与套接字
  • Cannot find module @rollup/rollup-win32-x64-msvc
  • Linux驱动学习笔记(五)
  • 解锁C++标准库:从理论到实战的进阶指南
  • 【电路笔记】-D型触发器