C++静态内存池

C++静态内存池

为何需要内存池

在我们为对象分配内存时,我们的编译器会自动在对象对应的内存上下各分配一个cookie,用来描述对象的大小信息,方便我们进行释放。

在我们大量进行内存分配是时候,过多的这些cookies会影响我们的性能。所以,我们希望对于一种对象,我们先分配一大块紧凑的内存(chunk),然后将大量的同种对象存储在这大块内存chunk中。每次需要一个这种对象时,我们不需要再次new一个(这样会创建新的cookie),而是直接从之前申请的那个大块内存中拿就可以。这样,我们就避开了cookie。这个大块的内存,就是我们的“池”。

如何实现内存池

我们申请了chunk之后,需要将其分割成一个个小块来分别存储每一个对象,而每个小块的大小需要可以容纳该对象。在实现时,我们通常根据具体应用场景以及经验,手动指定小块的大小。

每一个空闲的,未分配的小块之间使用单链表进行连接。每个小块就是单链表的一个节点node。当将这个小块被使用到时,就将它移除链表。回收时,将它重新加入链表。

#ifndef _StaticAllocator
#define _StaticAllocator
#include <iostream>
#include <cstddef>  // for std::size_t
#include <new>     // for std::bad_alloc

class StaticAllocator {
public:
    static constexpr std::size_t POOL_SIZE = 1024;  // 内存池的总大小
    static constexpr std::size_t BLOCK_SIZE = 32;   // 每个内存块的大小

    // 获取单例实例
    static StaticAllocator& getInstance() {
        static StaticAllocator instance;
        return instance;
    }

    // 禁用拷贝构造和赋值运算符
    StaticAllocator(const StaticAllocator&) = delete;
    StaticAllocator& operator=(const StaticAllocator&) = delete;

    // 分配内存块
    void* allocate() {
        if (!freeList) {
            throw std::bad_alloc();
        }
        void* block = freeList;
        freeList = freeList->next;
        return block;
    }

    // 释放内存块
    void deallocate(void* block) {
        Node* node = static_cast<Node*>(block);
        node->next = freeList;
        freeList = node;
    }
    StaticAllocator() {
        pool = ::operator new(POOL_SIZE);
        freeList = nullptr;

        // 初始化内存池,并将所有块加入空闲列表
        for (std::size_t i = 0; i < POOL_SIZE; i += BLOCK_SIZE) {
            deallocate(static_cast<char*>(pool) + i);
        }
    }

    ~StaticAllocator() {
        ::operator delete(pool);
    }
private:
    struct Node {
        Node* next;
    };

    void* pool;
    Node* freeList;
};

// 重载operator new和operator delete运算符,使用静态内存池
#define DECLARE_POOL_ALLOC()\
public:\
    static StaticAllocator allocator; \
static void* operator new(std::size_t size) {\
    if (size > StaticAllocator::BLOCK_SIZE) {\
        throw std::bad_alloc();\
    }\
    return allocator.allocate();\
}\
static void operator delete(void* ptr, std::size_t size) noexcept {\
    allocator.deallocate(ptr); \
}\

#define IMPLEMENT_POOL_ALLOC(classname) StaticAllocator classname::allocator;

#endif // !_StaticAllocator

问题:使用链表需要额外存储next指针,在这样是否造成额外开销,效果适得其反?

不会。内存池创建时,我们先分配chunk大小的内存块,然后将该内存块类型转换为node,并在其中存储next指针。在将该小内存块分配出去时,我们直接将该小内存块类型转换为对应的对象的类型,并存储其数据,next指针便被覆盖掉了。再次回收时,再将该内存块类型转换为node,并在其中存储next指针。可以认为next指针和对象之间是分时共用空间的关系。

下面是一个测试类

#ifndef _TESTCLASS
#define _TESTCLASS
#include "StaticAllocator.h"



// 示例类,使用静态内存池进行内存分配
class MyClass {
    DECLARE_POOL_ALLOC()

public:
    MyClass() {
        std::cout << "MyClass Constructor\n";
    }

    ~MyClass() {
        std::cout << "MyClass Destructor\n";
    }

private:
    int data[7];  // 使对象大小接近32字节
};

IMPLEMENT_POOL_ALLOC(MyClass)
#endif

main.cpp

#include"TestClass.h"
#include<iostream>
int main() {
    try {
        MyClass* obj1 = new MyClass();
        MyClass* obj2 = new MyClass();
        MyClass* obj3 = new MyClass();

        std::cout << obj1 << std::endl;
        std::cout << obj2 << std::endl;
        std::cout << obj3 << std::endl;

        delete obj1;
        delete obj2;
        delete obj3;
    }
    catch (const std::bad_alloc& e) {
        std::cerr << "Allocation failed: " << e.what() << std::endl;
    }

    return 0;
}

运行结果(地址是16位表示的)
执行结果

每两个对象之间都相差32的距离,是紧凑分配的。

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值