STL allocator作用

Allocator 是 STL 中负责内存管理 的组件,核心作用是把容器的数据结构逻辑和底层内存操作解耦。它有四个职责:分配原始内存(allocate)、释放内存(deallocate)、在已有内存上构造对象(construct)、析构对象但不释放内存(destroy)。通过模板参数注入不同的 allocator,可以在不改容器代码的情况下切换内存策略。

当不使用allocator时,容器的内存操作是硬编码的:

cpp 复制代码
// 伪代码:容器直接用 new/delete
template<typename T>
class Vector {
    void push_back(const T& val) {
        T* newbuf = new T[capacity * 2];    // 1. 申请内存 + 构造对象(绑死了)
        // ...
        delete[] oldbuf;                     // 2. 析构 + 释放内存(绑死了)
    }
};

问题:内存分配和对象构造被 new 绑死了。 你没法:

  • 用内存池(减少 malloc 开销)
  • 用共享内存(进程间通信)
  • 用自定义对齐(SIMD 场景)
  • 统计内存使用

allocator 把"要内存"和"构造对象"拆开了。

cpp 复制代码
template<typename T>
class allocator {
public:
    // ① 类型定义
    using value_type    = T;
    using pointer       = T*;
    using size_type     = size_t;

    // ② 内存分配(只申请裸内存,不构造对象!)
    T* allocate(size_t n) {
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    // ③ 内存释放(只归还内存,不析构对象!)
    void deallocate(T* p, size_t n) {
        ::operator delete(p);
    }

    // ④ 对象构造(在已有内存上 placement new)
    void construct(T* p, const T& val) {
        new(p) T(val);              // placement new
    }

    // ⑤ 对象析构(只调析构,不释放内存)
    void destroy(T* p) {
        p->~T();                    // 显式析构
    }
};
  1. allocate(n) --- 分配能容纳 n 个对象的原始内存,不调用构造函数
  2. deallocate(p, n) --- 释放之前分配的内存,不调用析构函数
  3. construct(p, args...) --- 在已分配的内存地址上用 placement new 构造对象
  4. destroy(p) --- 调用对象的析构函数,但不释放内存

这四步把"内存的生命周期"和"对象的生命周期"彻底分开了。容器可以先分配一大块内存(reserve),后面需要时再逐个构造对象,不用每次都 new/delete。

以vector::push_back为例:

cpp 复制代码
template<typename T, typename Alloc = allocator<T>>
class vector {
    T* start; size_t len, cap;
    Alloc alloc;   // 每个容器持有一个 allocator 实例

    void push_back(const T& val) {
        if (len == cap) {
            T* newbuf = alloc.allocate(cap * 2);       // 只要内存
            for (size_t i = 0; i < len; i++)
                alloc.construct(newbuf + i, start[i]);  // 逐个构造
            for (size_t i = 0; i < len; i++)
                alloc.destroy(start + i);               // 逐个析构旧对象
            alloc.deallocate(start, cap);               // 归还旧内存
            start = newbuf;
            cap *= 2;
        }
        alloc.construct(start + len, val);  // 构造新元素
        len++;
    }
};

allocate 和 new 的区别(重要)

cpp 复制代码
new T[n]       = operator new(n*sizeof(T))  +  n次构造函数
allocator      = allocate(n)                +  construct(p, val)
                     ↑ 只申请内存                ↑ 只构造对象
                     分开了!可以独立控制

以vector::reserve为例:

cpp 复制代码
void reserve(size_t n) {
    T* p = alloc.allocate(n);   // 只要内存,不构造!
    // 如果用 new T[n],会调用 n 次默认构造 → 完全多余
}

reserve 只要内存空间,不需要构造对象。用 new 会浪费 n 次默认构造开销。这就是 allocator 把分配和构造拆开的核心价值。

rebind机制

容器内部不只分配用户指定类型的内存。比如 list<int> 内部需要分配的是节点 _Node<int>,不是 int。rebind 就是让 allocator<int> 能"重新绑定"为 allocator<_Node<int>>,这样容器内部的各种辅助结构也能用同一套内存策略。

核心矛盾

cpp 复制代码
// list 内部的真实节点结构
struct _List_node {
    _List_node* prev;
    _List_node* next;
    int data;          // ← T 在这里
};

容器需要分配的是 _List_node,不是 int。但用户只给了 allocator<int>,怎么拿到 allocator<_List_node>

rebind工作模式

cpp 复制代码
template<typename T>
class allocator {
public:
    // rebind 是一个嵌套模板结构体
    template<typename U>
    struct rebind {
        using other = allocator<U>;   // "把 T 换成 U"
    };
    
    // ... allocate, deallocate 等
};

用法:

cpp 复制代码
using AllocInt = allocator<int>;
using AllocNode = AllocInt::rebind<_List_node>::other;
// allocator<int> → allocator<_List_node>

rebind<_List_node>::other 就是 allocator<_List_node>------类型变了,但分配策略(内存池、自定义逻辑等)保持一致。

容器内部用法

以list为例:

cpp 复制代码
template<typename T, typename Alloc = allocator<T>>
class list {
    // 节点类型
    struct _Node {
        _Node* prev;
        _Node* next;
        T data;
    };

    // 关键!把 allocator<T> rebind 成 allocator<_Node>
    using node_alloc = typename Alloc::template rebind<_Node>::other;
    
    node_alloc alloc;   // 实际用来分配节点的 allocator

    _Node* create_node(const T& val) {
        _Node* p = alloc.allocate(1);       // 分配一个 _Node 大小的内存
        alloc.construct(p, _Node{nullptr, nullptr, val});  // 构造节点
        return p;
    }
};

每种容器 rebind 到什么

容器 用户给的 T 实际分配的类型 为什么不同
vector<T> T T 不需要 rebind,vector 直接存 T
list<T> T _List_node<T> 节点带前后指针
map<K,V> pair<const K,V> _RB_tree_node<pair<const K,V>> 红黑树节点带颜色+父子指针
unordered_map<K,V> pair<const K,V> 哈希桶节点 带_next指针
string char char 不需要 rebind
cpp 复制代码
用户写:list<int, MyAllocator<int>>

   MyAllocator<int>
         │
         ▼  rebind
   MyAllocator<_List_node<int>>
         │
         ▼  allocate(1)
   返回 _List_node<int>* 的裸内存
         │
         ▼  construct(p, ...)
   placement new 构造节点
相关推荐
小小编程路1 小时前
C++ STL 原理与性能
开发语言·c++
码不停蹄的玄黓1 小时前
Java线程池生命周期
java·开发语言
Kingairy1 小时前
LUA环境搭建
开发语言·lua
小欣加油1 小时前
leetcode239 滑动窗口最大值
数据结构·c++·算法·leetcode·哈希算法
z落落1 小时前
C# 虚方法(virtual)与抽象方法 +区别+new方法隐藏 & override方法重写
java·开发语言·c#
玖釉-1 小时前
Vulkan 示例解析:pipelines.cpp 如何在一个 Render Pass 中切换多条 Graphics Pipeline
c++·windows·算法·图形渲染
Ada's1 小时前
【计算机基础系列】python语言:环境搭建
开发语言·python
xiaoshuaishuai81 小时前
C# Avalonia UI的ItemControl
开发语言·ui·c#
未若君雅裁1 小时前
JMM、volatile 与 CAS:并发安全三大问题
java·开发语言