菜鸟笔记
提升您的技术认知

c stl 容器内存池-ag真人官方网

c 中,为了优化性能,减少频繁的内存分配和释放操作,可以自定义一个内存池分配器,并将其与 stl 容器结合使用。以下是实现自定义内存池分配器的完整教程。


1. 为什么需要自定义内存池分配器

  1. 减少动态分配的开销
    • 使用 newmalloc 动态分配内存会带来较大的开销,尤其是在频繁分配和释放小块内存时。
    • 内存池通过预分配大块内存并按需管理小块,可以显著减少这种开销。
  2. 提升内存访问性能
    • 内存池分配的内存通常是连续的,这有利于缓存友好性(cache locality),从而提高运行速度。
  3. 自定义分配器与 stl 容器结合
    • stl 容器(如 std::vector, std::map 等)支持自定义分配器,用于控制内存分配和释放行为。
    • 使用内存池分配器可以优化 stl 容器的性能。

2. 内存池分配器的设计思路

一个简单的内存池需要包含以下功能:
1. 预分配内存块
– 在初始化时,分配一大块连续内存,用于后续的小块分配。

  1. 小块内存分配
    • 从已分配的内存块中划分小块,按需分配给用户。
  2. 内存释放和重用
    • 用户释放的小块内存可以回收到内存池中,以便再次分配。

3. 实现自定义内存池分配器

以下是完整实现,包括内存池类和与 stl 容器结合使用的分配器。

3.1 内存池实现

#include 
#include 
#include 
#include 
class memorypool {
public:
    memorypool(size_t blocksize, size_t blockcount)
        : _blocksize(blocksize), _blockcount(blockcount) {
        allocateblock();
    }
    ~memorypool() {
        for (void* block : _memoryblocks) {
            ::operator delete(block); // 释放所有内存块
        }
    }
    void* allocate() {
        if (_freeblocks.empty()) {
            allocateblock();  // 如果没有空闲块,分配新的内存块
        }
        void* ptr = _freeblocks.back();
        _freeblocks.pop_back();  // 从空闲块中取一个
        return ptr;
    }
    void deallocate(void* ptr) {
        _freeblocks.push_back(ptr);  // 将释放的块放回空闲列表
    }
private:
    size_t _blocksize;                 // 每个小块的大小
    size_t _blockcount;                // 每个内存块包含的小块数
    std::vector _freeblocks;    // 空闲的小块列表
    std::vector _memoryblocks;  // 已分配的内存块
    void allocateblock() {
        // 分配一整块内存
        void* block = ::operator new(_blocksize * _blockcount);
        _memoryblocks.push_back(block);
        // 将分配的内存划分为小块,加入空闲列表
        for (size_t i = 0; i < _blockcount;   i) {
            _freeblocks.push_back(static_cast(block)   i * _blocksize);
        }
    }
};

3.2 自定义分配器

实现符合 stl 的分配器接口(即 std::allocator 的行为)。

template 
class poolallocator {
public:
    using value_type = t;
    poolallocator(size_t blockcount = 1024) 
        : _pool(sizeof(t), blockcount) {}
    template 
    poolallocator(const poolallocator& other) noexcept
        : _pool(other._pool) {}
    t* allocate(size_t n) {
        if (n != 1) {
            throw std::bad_alloc(); // 只支持单个对象的分配
        }
        return static_cast(_pool.allocate());
    }
    void deallocate(t* ptr, size_t n) {
        if (n != 1) {
            return; // 只支持单个对象的释放
        }
        _pool.deallocate(ptr);
    }
    template 
    struct rebind {
        using other = poolallocator;
    };
private:
    memorypool& _pool;
    template 
    friend class poolallocator;
};

3.3 将内存池分配器与 stl 容器结合

可以将自定义分配器与任意支持分配器的 stl 容器结合使用,例如 std::vectorstd::liststd::map 等。

示例:使用 std::vector 和内存池分配器

#include 
#include 
int main() {
    // 使用自定义内存池分配器
    poolallocator allocator;
    // 将分配器绑定到 std::vector
    std::vector> vec(allocator);
    // 插入数据
    for (int i = 0; i < 10;   i) {
        vec.push_back(i);
    }
    // 输出数据
    for (int value : vec) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
    return 0;
}

3.4 输出结果

运行代码时,std::vector 将使用自定义的内存池分配器来管理内存,输出如下:

0 1 2 3 4 5 6 7 8 9

4. 优化和注意事项

  1. 线程安全
    • 如果需要在多线程环境下使用,可以为 memorypool 添加互斥锁(如 std::mutex)来保护 _freeblocks_memoryblocks
  2. 对象构造与析构
    • stl 容器会调用元素的构造和析构函数,因此需要确保内存池分配的内存支持对象的正确构造和析构。
    • 可以通过 std::allocator_traitsstd::construct_at 明确调用构造函数
  3. 支持多类型内存分配
    • 内存池可以设计为支持不同大小的对象分配,通过模板实现多类型支持。
  4. 内存池清理
    • 在析构时释放所有内存,确保没有内存泄漏

5. 总结

  1. 使用自定义内存池分配器可以显著提高小内存分配的性能,特别是在高频率内存分配释放场景中。
  2. 自定义内存池分配器需要实现分配(allocate)和释放(deallocate)接口,并与 stl 容器结合。
  3. 在多线程环境中使用时,需要考虑线程安全性。
  4. 内存池适合性能敏感的场景,但在普通场景下可以优先使用标准分配器(std::allocator)。
网站地图