C++ 内存分配器的作用

在 C++ 中,内存分配器(Allocator) 是一个用于管理内存分配和释放的机制。

📚 一、内存分配器基础

默认分配器:std::allocator<T>

C++ 标准库提供了一个默认的分配器模板类 std::allocator<T>,它封装了底层的内存分配和释放操作。默认分配器通过调用全局 operator newoperator delete 来分配和释放内存。

基本方法:
  • allocate(n):分配原始未构造的内存空间,能容纳 n 个 T 类型对象。
  • deallocate(p, n):释放由 allocate() 分配的内存,p 是指针,n 是元素数。
  • construct(p, args…):在已分配的内存 p 上构造一个对象。
  • destroy§:销毁 p 指向的对象。

✅ 二、使用默认分配器 vs. 自定义分配器

使用默认分配器

当我们创建一个标准容器(如 std::vector),如果没有指定分配器,则会使用默认分配器 std::allocator<T>。这意味着每次需要扩展容器容量时,都会调用 newdelete 来分配和释放内存。

示例代码:
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
        std::cout << "Vector size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;
    }
    return 0;
}

输出示例:

Vector size: 1, capacity: 1
Vector size: 2, capacity: 2
Vector size: 3, capacity: 4
Vector size: 4, capacity: 4
Vector size: 5, capacity: 8
Vector size: 6, capacity: 8
Vector size: 7, capacity: 8
Vector size: 8, capacity: 8
Vector size: 9, capacity: 16
Vector size: 10, capacity: 16

可以看到,随着元素数量的增加,vector 的容量会按照一定的策略增长(通常是翻倍),这会导致多次调用 newdelete,带来一定的性能开销。

自定义分配器示例:

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

template<typename T>
class SimplePoolAllocator {
public:
    using value_type = T;

    SimplePoolAllocator() noexcept = default;

    template<typename U>
    SimplePoolAllocator(const SimplePoolAllocator<U>&) noexcept {}

    T* allocate(std::size_t n) {
        std::cout << "Allocating " << n << " elements of type " << typeid(T).name() << std::endl;
        return static_cast<T*>(::operator new(n * sizeof(T)));
    }

    void deallocate(T* p, std::size_t n) {
        std::cout << "Deallocating " << n << " elements" << std::endl;
        ::operator delete(p);
    }
};

template<typename T, typename U>
bool operator==(const SimplePoolAllocator<T>&, const SimplePoolAllocator<U>&) { return true; }

template<typename T, typename U>
bool operator!=(const SimplePoolAllocator<T>&, const SimplePoolAllocator<U>&) { return false; }

int main() {
    std::vector<int, SimplePoolAllocator<int>> vec;
    for (int i = 0; i < 10; ++i) {
        vec.push_back(i);
        std::cout << "Vector size: " << vec.size() << ", capacity: " << vec.capacity() << std::endl;
    }
    return 0;
}

输出示例:

Allocating 1 elements of type i
Vector size: 1, capacity: 1
Allocating 2 elements of type i
Deallocating 1 elements
Vector size: 2, capacity: 2
Allocating 4 elements of type i
Deallocating 2 elements
Vector size: 3, capacity: 4
...

在这个例子中,我们简单地记录了内存分配和释放的操作。

当然可以!在实际应用中,自定义分配器(Custom Allocator) 最常见的用途之一就是实现 内存池(Memory Pool)。内存池是一种预分配固定大小的内存块集合,避免频繁调用 newdelete,从而大幅提升性能,尤其适用于高频创建和销毁对象的场景。


三、应用场景:游戏开发中的对象池

以一个游戏引擎为例,游戏中可能会频繁创建和销毁“子弹”对象。如果每次都使用默认分配器进行动态内存分配,会导致:

  • 高频调用 malloc/freenew/delete
  • 内存碎片
  • 分配速度慢

我们可以使用一个 内存池分配器 来解决这些问题。


📦示例:带内存池的自定义分配器(用于 std::vector

我们将实现一个简单的固定大小内存池分配器(Fixed Block Memory Pool Allocator),并将其用于 std::vector

✅ 示例目标:

  • 实现一个能管理固定大小对象的内存池。
  • 将其作为 std::vector 的自定义分配器。
  • 提升频繁分配/释放小对象时的性能。

🔨 步骤 1:实现内存池类

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

// 固定大小内存池
class FixedBlockAllocator {
private:
    struct FreeListNode {
        FreeListNode* next;
    };

    size_t block_size_;
    size_t pool_size_;
    char* memory_pool_;
    FreeListNode* free_list_;

public:
    FixedBlockAllocator(size_t block_size, size_t num_blocks)
        : block_size_(block_size), pool_size_(num_blocks) {
        assert(block_size >= sizeof(FreeListNode));

        memory_pool_ = reinterpret_cast<char*>(::operator new(block_size * num_blocks));
        free_list_ = reinterpret_cast<FreeListNode*>(memory_pool_);

        // 初始化空闲链表
        for (size_t i = 0; i < num_blocks - 1; ++i) {
            FreeListNode* node = reinterpret_cast<FreeListNode*>(memory_pool_ + i * block_size_);
            node->next = reinterpret_cast<FreeListNode*>(memory_pool_ + (i + 1) * block_size_);
        }

        FreeListNode* last_node = reinterpret_cast<FreeListNode*>(memory_pool_ + (num_blocks - 1) * block_size_);
        last_node->next = nullptr;
    }

    ~FixedBlockAllocator() {
        ::operator delete(memory_pool_);
    }

    void* allocate() {
        if (!free_list_) {
            return ::operator new(block_size_); // 超出池容量,退化为普通 new
        }

        void* p = free_list_;
        free_list_ = free_list_->next;
        return p;
    }

    void deallocate(void* p) {
        if (p >= memory_pool_ && p < memory_pool_ + block_size_ * pool_size_) {
            // 归还到内存池
            FreeListNode* node = static_cast<FreeListNode*>(p);
            node->next = free_list_;
            free_list_ = node;
        } else {
            // 不属于内存池,使用 delete
            ::operator delete(p);
        }
    }
};

🔨 步骤 2:实现自定义分配器适配器

为了让这个内存池可以作为 STL 容器的分配器,我们需要封装成标准接口。

template <typename T, size_t BLOCK_SIZE = 1024>
class PoolAllocator {
private:
    FixedBlockAllocator& pool_;

public:
    using value_type = T;

    PoolAllocator(FixedBlockAllocator& pool) noexcept : pool_(pool) {}

    template <typename U>
    PoolAllocator(const PoolAllocator<U, BLOCK_SIZE>& other) noexcept
        : pool_(other.pool_) {}

    T* allocate(std::size_t n) {
        assert(n == 1); // 只支持单个对象分配
        return static_cast<T*>(pool_.allocate());
    }

    void deallocate(T* p, std::size_t n) {
        assert(n == 1);
        pool_.deallocate(p);
    }
};

template <typename T, typename U, size_t BS>
bool operator==(const PoolAllocator<T, BS>& a, const PoolAllocator<U, BS>& b) {
    return &a.pool_ == &b.pool_;
}

template <typename T, typename U, size_t BS>
bool operator!=(const PoolAllocator<T, BS>& a, const PoolAllocator<U, BS>& b) {
    return !(a == b);
}

🔨 步骤 3:使用内存池分配器的 std::vector

我们来测试一下这个分配器在 std::vector 中的表现。

struct Bullet {
    float x, y, angle;
    int damage;
};

int main() {
    const size_t POOL_SIZE = 1000;
    FixedBlockAllocator pool(sizeof(Bullet), POOL_SIZE);

    using BulletVector = std::vector<Bullet, PoolAllocator<Bullet>>;

    BulletVector bullets(PoolAllocator<Bullet>(pool));

    // 插入大量子弹对象
    for (int i = 0; i < 2000; ++i) {
        bullets.push_back({1.0f, 2.0f, 0.5f, 10});
    }

    std::cout << "Total elements: " << bullets.size() << std::endl;

    return 0;
}

📈 三、性能优势分析

指标默认分配器 (std::allocator)自定义内存池分配器
内存分配速度慢(每次调用 new快(从链表取)
内存释放速度慢(每次调用 delete极快(归还链表)
内存碎片几乎无
扩展性不可控可定制(如缓存、日志等)

在本例中,前 1000 次分配使用内存池,后 1000 次自动回退到 new,你可以根据需要扩展内存池大小或实现多级内存池。

🛠️ 四、示例进阶

要支持 不同大小对象的分配,我们可以构建一个更通用的内存池系统,即所谓的 Slab Allocator(分块分配器)多级内存池(Multi-block Memory Pool)

这种分配器可以管理多个固定大小的内存池,每个池专门处理某一类大小的对象。当请求分配时,根据对象大小选择合适的池进行分配,从而避免频繁调用 new/delete,减少内存碎片并提升性能。


🧱 一、设计目标
  • 支持任意大小对象的分配。
  • 内部维护多个固定大小的内存池(例如:4B、8B、16B、32B…)。
  • 对于大于某个阈值的对象(如 1024B),直接使用默认分配器。
  • 提供标准接口用于 STL 容器或自定义类型。

🔨 二、实现步骤

我们将构建以下组件:

  1. MemoryPoolManager:主控类,管理多个固定大小的内存池。
  2. FixedSizePool:单个固定大小的内存池(类似前面的 FixedBlockAllocator)。
  3. GeneralAllocator:对外的标准分配器接口,适配 STL 容器。

✅ 示例代码:支持多种大小对象分配的通用分配器
步骤 1:固定大小内存池类
// FixedSizePool.h
#pragma once
#include <cstddef>
#include <vector>
#include <cassert>

class FixedSizePool {
private:
    size_t block_size_;
    size_t num_blocks_;
    void* memory_pool_;
    void** free_list_;

public:
    FixedSizePool(size_t block_size, size_t num_blocks);
    ~FixedSizePool();

    void* allocate();
    void deallocate(void* p);

    size_t block_size() const { return block_size_; }
};
// FixedSizePool.cpp
#include "FixedSizePool.h"
#include <iostream>
#include <cstdlib>

FixedSizePool::FixedSizePool(size_t block_size, size_t num_blocks)
    : block_size_(block_size), num_blocks_(num_blocks) {
    assert(block_size >= sizeof(void*)); // 至少能放一个指针
    memory_pool_ = std::malloc(block_size * num_blocks_);
    if (!memory_pool_) throw std::bad_alloc();

    free_list_ = reinterpret_cast<void**>(memory_pool_);

    char* pool_bytes = reinterpret_cast<char*>(memory_pool_);
    for (size_t i = 0; i < num_blocks_ - 1; ++i) {
        free_list_[i] = pool_bytes + block_size_ * (i + 1);
    }
    free_list_[num_blocks_ - 1] = nullptr;
}

FixedSizePool::~FixedSizePool() {
    std::free(memory_pool_);
}

void* FixedSizePool::allocate() {
    if (!free_list_) return nullptr;

    void* p = free_list_;
    free_list_ = reinterpret_cast<void**>(*free_list_);
    return p;
}

void FixedSizePool::deallocate(void* p) {
    if (!p) return;

    // 检查是否在内存池范围内
    char* start = reinterpret_cast<char*>(memory_pool_);
    char* end = start + block_size_ * num_blocks_;

    if (p >= start && p < end) {
        *reinterpret_cast<void**>(p) = free_list_;
        free_list_ = reinterpret_cast<void**>(p);
    }
}

步骤 2:多级内存池管理器
// MemoryPoolManager.h
#pragma once
#include "FixedSizePool.h"
#include <unordered_map>
#include <vector>
#include <mutex>

class MemoryPoolManager {
private:
    std::vector<FixedSizePool> pools_;
    std::unordered_map<size_t, FixedSizePool*> size_to_pool_;
    std::mutex mtx_;

public:
    static MemoryPoolManager& instance() {
        static MemoryPoolManager manager;
        return manager;
    }

    void initialize();

    void* allocate(size_t size);
    void deallocate(void* ptr, size_t size);
};
// MemoryPoolManager.cpp
#include "MemoryPoolManager.h"
#include <iostream>

void MemoryPoolManager::initialize() {
    static const size_t pool_sizes[] = { 4, 8, 16, 32, 64, 128, 256, 512 };
    for (auto sz : pool_sizes) {
        pools_.emplace_back(FixedSizePool(sz, 1000));
    }

    for (auto& pool : pools_) {
        size_to_pool_[pool.block_size()] = &pool;
    }
}

void* MemoryPoolManager::allocate(size_t size) {
    std::lock_guard<std::mutex> lock(mtx_);

    // 找到合适大小的池(向上对齐)
    for (const auto& kv : size_to_pool_) {
        if (kv.first >= size) {
            void* p = kv.second->allocate();
            if (p) return p;
        }
    }

    // 超出预设池范围,使用默认 new
    return ::operator new(size);
}

void MemoryPoolManager::deallocate(void* ptr, size_t size) {
    std::lock_guard<std::mutex> lock(mtx_);

    for (const auto& kv : size_to_pool_) {
        if (kv.first >= size) {
            kv.second->deallocate(ptr);
            return;
        }
    }

    // 非池内内存,使用 delete
    ::operator delete(ptr);
}

步骤 3:自定义通用分配器(适配 STL)
// GeneralAllocator.h
#pragma once
#include <memory>
#include "MemoryPoolManager.h"

template<typename T>
class GeneralAllocator {
public:
    using value_type = T;

    GeneralAllocator() noexcept = default;

    template<typename U>
    GeneralAllocator(const GeneralAllocator<U>&) noexcept {}

    T* allocate(std::size_t n) {
        if (n != 1) throw std::bad_alloc(); // 本例只支持单对象分配
        return static_cast<T*>(MemoryPoolManager::instance().allocate(sizeof(T)));
    }

    void deallocate(T* p, std::size_t n) {
        if (n != 1) return;
        MemoryPoolManager::instance().deallocate(p, sizeof(T));
    }
};

template<typename T, typename U>
bool operator==(const GeneralAllocator<T>&, const GeneralAllocator<U>&) {
    return true;
}

template<typename T, typename U>
bool operator!=(const GeneralAllocator<T>& a, const GeneralAllocator<U>& b) {
    return false;
}

✅ 四、使用示例:为多种对象提供内存池服务
#include <vector>
#include <list>
#include <string>
#include "GeneralAllocator.h"

int main() {
    // 初始化内存池
    MemoryPoolManager::instance().initialize();

    // 使用通用分配器创建容器
    using MyAlloc = GeneralAllocator<int>;
    std::vector<int, MyAlloc> vec;
    for (int i = 0; i < 1000; ++i) {
        vec.push_back(i);
    }

    // 不同大小对象也能共享内存池
    std::list<std::string, GeneralAllocator<std::string>> str_list;
    for (int i = 0; i < 100; ++i) {
        str_list.emplace_back("Hello");
    }

    return 0;
}

📈 五、优势总结
特性描述
支持任意大小对象根据大小选择最适合的内存池
减少内存碎片同一类大小对象使用相同池
提高性能避免频繁调用 new/delete
可扩展性强可以添加线程安全、日志等功能
兼容 STL作为分配器可插入任意容器

🧱 四、自定义分配器的优势

性能优化

通过自定义分配器,特别是实现内存池技术,可以显著减少内存碎片化并加快分配速度。例如,在游戏开发或高性能计算领域,使用定制化的内存池可以避免频繁的 new/delete 操作带来的性能损失。

资源隔离

不同的模块可以使用独立的分配器,从而避免不同模块间的内存污染问题。这对于大型项目尤其重要,可以帮助定位内存泄漏等问题。

日志和调试

可以在自定义分配器中添加日志记录功能,方便追踪内存分配和释放的情况,有助于检测潜在的内存泄漏或性能瓶颈。


🧪 五、总结

方案优点缺点
使用默认分配器简单易用,适用于大多数场景可能在高并发或高性能要求下表现不佳
使用自定义分配器可根据需求优化内存分配策略,提升性能实现复杂,容易出错

对于大多数应用程序来说,默认分配器已经足够好。然而,在特定场景下(如高性能要求、资源受限环境),自定义分配器可以带来显著的好处。


📘 六、参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员乐逍遥

如果你觉得帮助了你,支持一下!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值