前言
本篇文章主要内容为讲述自己对于C++11内存池项目(一位大佬所写)的解析(注意这里是我记录一下自己对项目的解析)。初次上手项目,很多知识点都没有遇到过,有些地方会提供其他的博文帮助理解,有描述不清楚或存在错误的地方还请大家一一指出。
【源码剖析】MemoryPool —— 简单高效的内存池 allocator 实现
项目源码:GitHub源码
项目介绍
内存池是什么
话不多说,这里摘录最具权威的原作者对于项目的解释:
什么是内存池?什么是 C++ 的 allocator?
内存池简单说,是为了减少频繁使用 malloc/free new/delete 等系统调用而造成的性能损耗而设计的。当我们的程序需要频繁地申请和释放内存时,频繁地使用内存管理的系统调用可能会造成性能的瓶颈,嗯,是可能,毕竟操作系统的设计也不是盖的(麻麻说把话说太满会被打脸的(⊙v⊙))。内存池的思想是申请较大的一块内存(不够时继续申请),之后把内存管理放在应用层执行,减少系统调用的开销。
allocator详解
那么,allocator 呢?它默默的工作在 C++ 所有容器的内存分配上。默默贴几个链接吧:
当你对 allocator 有基本的了解之后,再看这个项目应该会有恍然大悟的感觉,因为这个内存池是以一个 allocator 的标准来实现的。一开始不明白项目里很多函数的定义是为了什么,结果初步了解了 allocator 后才知道大部分是标准接口。这样一个 memory pool allocator 可以与大多数 STL 容器兼容,也可以应用于你自定义的类。像作者给出的例子 —— test.cpp, 是用一个基于自己写的 stack 来做 memory pool allocator 和 std::allocator 性能的对比 —— 最后当然是 memory pool allocator 更优。
内存池是一个一个的 block 以链表的形式连接起来,每一个 block 是一块大的内存,当内存池的内存不足的时候,就会向操作系统申请新的
block 加入链表。还有一个 freeSlots_ 的链表,链表里面的每一项都是对象被释放后归还给内存池的空间,内存池刚创建时
freeSlots_> 是空的,之后随着用户创建对象,再将对象释放掉,这时候要把内存归还给内存池,怎么归还呢?就是把指向这个对象的内存的指针加到
freeSlots_ 链表的前面(前插)。
用户在创建对象的时候,先检查 freeSlots_ 是否为空,不为空的时候直接取出一项作为分配出的空间。否则就在当前 block 内取出一个 Slot_ 大小的内存分配出去,如果 block 里面的内存已经使用完了呢?就向操作系统申请一个新的 block。内存池工作期间的内存只会增长,不释放给操作系统。直到内存池销毁的时候,才把所有的 block delete 掉。
注释源码: 点我到注释源码
下图左一为刚申请的Blcok↓
MemoryPool.tcc
该文件对Memory.h文件中声明过的函数进行实现,在此一些简单的类函数就不再一一赘述,只挑选出部分函数给出详细剖析,其余可在最后的代码汇总中查看注释。
allocateBlock 创建Block块
函数用于申请一块Block内存块,并使用头插法放入Blcok内存块链表。
//申请一块新的分区block
template <typename T, size_t BlockSize>
void
MemoryPool<T, BlockSize>::allocateBlock()
{
// Allocate space for the new block and store a pointer to the previous one
//申请 BlockSize字节大小的一块空间并用char* 类型指针接收
data_pointer_ newBlock = reinterpret_cast<data_pointer_>
(operator new(BlockSize));
//newBlock成为新的block内存首址
reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
//currentBlock 更新为 newBlock的位置
currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
// Pad block body to staisfy the alignment requirements for elements
//保留第一个slot 用于Block链表的链接
data_pointer_ body = newBlock + sizeof(slot_pointer_);
//求解空间对齐需要的字节数
size_type bodyPadding = padPointer(body, alignof(slot_type_));
//若出现残缺不可以作为一个slot的空间,跳过这块空间作为第一个可用slot的位置
currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
lastSlot_ = reinterpret_cast<slot_pointer_>
(newBlock + BlockSize - sizeof(slot_type_) + 1);
//始址: newBlock 块大小 BlockSize 末址 newBlock + BlockSize 末址减去一个slot槽大小
//得到倒数第二个slot的末址 再加一得到最后一块slot的始址
}
这里需要注意的是:在生成一块BlockSize字节大小的Block内存块时需要保留Slot类型大小的空间用于后续Block链表的链接。对应图中的蓝色内存区。此外,在调用padPointer函数 的时候传入的参数使用了 C++11 的新特性 C++11 内存对齐 alignof alignas
padPointer 空间对齐
每一个Block内存块的大小(BlockSize字节)并不是刚好为Slot槽大小的整数倍(取决于使用者定义的BlockSize),例如一个内存块Block大小设置为BlockSize = 11 bytes,每个Slot槽存放数据的大小为4字节时,BlockSize % sizeof(Slot) = 11 % 4 = 2 … 3,余下的三个字节是不足一个Slot槽所需空间的,这时我们必须略去这些多余的空间。上述空间在图中用橙色区域进行了表示。
//计算对齐所需补的空间
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
const noexcept
{
uintptr_t result = reinterpret_cast<uintptr_t>(p);//将char* 类型的指针转换为 uintptr_t 类型(无符号整型)
return ((align - result) % align);//多余不够一个Slot槽大小的空间,需要跳过
}
MemoryPool 构造函数
这里说明一下内存池中使用到的指针,先看看原作者是怎么描述的:
slot_pointer_ currentBlock_; // 内存块链表的头指针
slot_pointer_ currentSlot_; // 元素链表的头指针
slot_pointer_ lastSlot_; // 可存放元素的最后指针
slot_pointer_ freeSlots_; // 元素构造后释放掉的内存链表头指针
当时阅读项目时很不解的点就是 currentSlot 指针的含义,作者给出的解释是元素链表的头指针 ,我思来想去,每一个Slot槽是一个联合体:
union Slot_ {
value_type element;//使用时为 value_type 类型
Slot_* next;//需要回收时为 Slot_* 类型并加入 空闲链表中
};
当Slot用来存放数据时,根据联合的特性,另一个指针属性是不可以使用的,这两个属性的关系也很简单,不可同时出现。因此当Slot槽存储数据时不可能同时是一个链表(链表至少需要一个指针),正是这样,再根据后面对于currentSlot指针的使用我将其解释为
指向第一个可用元素 Slot ,这是狭义上的第一个可用,在后面allocate函数中为每一个元素分配空间的时候currentSlot指针是明确的只能往后走的,也就是说这个 “可用” 不包括已经在空闲链表上的Slot(使用过但现在释放并还给内存池的槽),指向的是从没有被置放过数据的Slot槽。
//构造函数 初始化所有指针为 nullptr
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool()
noexcept
{
currentBlock_ = nullptr;//指向第一块Block区 即Block内存块链表的头指针
currentSlot_ = nullptr;//当前第一个可用槽的位置
lastSlot_ = nullptr;//最后可用Slot的位置
freeSlots_ = nullptr;//空闲链表头指针
}
allocate 内存分配
为每个element元素分配内存,先查询槽的空闲链表是否为空,若不为空则直接将链表头结点表示的空间分配出去,反之到Block中分割新的Slot大小的空间。
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::allocate(size_type n, const_pointer hint)
{
//在内存池中申请 n 个Node,hint 默认设置为 空
if (freeSlots_ != nullptr) {
//空闲链表不为空,表示链表中有slot可以使用
pointer result = reinterpret_cast<pointer>(freeSlots_);//将头结点分配出去
freeSlots_ = freeSlots_->next;//头结点更新
return result;
}
else {
//空闲链表没有 slot
//如果当前可用slot槽已经到了最后的位置,用完之后需要再创建一块Block方便后续操作
if (currentSlot_ >= lastSlot_)
allocateBlock();
return reinterpret_cast<pointer>(currentSlot_++);//返回分配的空间并更新 currentSlot
//currentSlot 指向Block内存块中最前面可用的Slot槽 (freeSlot链表中不算)
}
}
当 currentSlot 到了 lastSlot 的位置表明该Block即将用完,必须另起炉灶,再向操作系统申请新的Block内存块。这里有一个疑点
if (currentSlot_ >= lastSlot_)
allocateBlock();
也就是说在 currentSlot 指针等于 lastSlot 的时候调用allocateBlock函数,而在此时这块空间还没有被分配出去,调用函数时我们注意到在函数中currentSlot指针被修改指向了新的内存块Block中的空间了!!!
currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
也就是说之前的内存块中最后一个Slot槽大小的空间还未被分配出去就已经改变了currentSlot指针,于是我将if条件修改为了
if (currentSlot_ > lastSlot_)
allocateBlock();
再次运行时就报错了,,,
deallocate 释放内存
这里的释放内存并不是将这块空间真正的释放掉了(归还给操作系统),而是归还给内存池。这样使得内存池的空间可复用性更高,不需要频繁机械地向OS申请内存,那么这里又是如何将释放掉的Slot槽回收起来呢?考虑到Slot槽并不一定是连续释放的,因此使用链表的形式对这些离散分布的内存槽进行连接,既然是链表,那么必须有指针来完成链表的链接工作,这个指针就大有来头了,回顾一下 Slot 槽的内部结构:
union Slot_ {
value_type element;//使用时为 value_type 类型
Slot_* next;//需要回收时为 Slot_* 类型并加入 空闲链表中
};
既然使用了联合,那么很显然就是用来节省内存的,说到这里可以猜测到槽中的 Slot* 属性是用来充当链表中的指针,当我们创建对象的时候使用 value_type 也就是普通值类型,而销毁数据之后就可以使用它的另一属性将其放入空闲链表中。下图为一块Block块空闲链表的链接情况:
deallocate具体操作:
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deallocate(pointer p, size_type n)
{
if (p != nullptr) {
reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;//union指针域被赋值 之前的数据销毁
freeSlots_ = reinterpret_cast<slot_pointer_>(p);//空闲链表头结点更新
}
}
使用头插法维护空闲链表,当有空间请求时优先考虑空闲链表,若链表非空,则将链表头指针所指空间分配出去。
注意:这里的链表只有一个指针域,其实就是一串指针保存了被释放Slot的地址
max_size 计算最大可用槽的个数
先放上代码:
//计算最大可用槽Slot的个数
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::max_size()
const noexcept
{
//无符号整型 unsiged int 类型运算,先将 -1 转换为无符号整型 即无符号整型最大值
size_type maxBlocks = -1 / BlockSize;//无符号类型数值可以表示的Block的个数
//unsigned int MAX / BlockSize 向下取整
return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
// (BlockSize - sizeof(data_pointer)) / sizeof(slot_type_) →指每一块Block中可以存放的slot槽个数
// 最大可以有 maxBlocks 块内存块
//因此最大的可用槽个数 为每一块Block可以使用的Slot槽个数乘以最大可用Block
}
这个函数的作用就是通过计算确定最大可用的Slot的个数,这样可以防止越界导致数据损坏(越界之后的地址由于是循环计数机制会回到起点,对已经存储数据的空间进行破坏),这里的计算方式很有意思,我们一起看看吧
size_type maxBlocks = -1 / BlockSize;
这里的 size_type 是被 typedef 过的 unsigned int, BlockSize为 size_t (同无符号整型), 注意这个分数的上部,是不是很有意思,用一个负数去进行运算,最后赋值给无符号的数值类型,先别凌乱,慢慢来分析:
首先计算机内部为了能够更加轻松自然地去进行负数运算,选择原码是万万不可的,打个比方:
32位系统中原码的表示为 最高位为符号位,其余31位为数值位。
若使用原码进行计算:
2 + (-1)竟然等于 -3!!!很明显这不可行。最终科学家们找到一种很实用的方式就是补码运算
使用补码的好处
负数的补码等于其反码加1。
求得-1的补码其实就是 unsigned int 类型可以表示的最大值,那么 maxBlock 表示的就是内存池最多能生成的内存块Block的数量。而返回的是一个表达式:
return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
BlockSize - sizeof(data_pointer) 表示每一个Block块除去一个指针大小之后的可用空间,“ (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) ”表示一个Block块真正可以使用来存储数据的Slot槽的个数,每一块Block可以用的槽个数再乘上内存块的最大个数即可得到一个内存池可以使用的Slot槽的个数。
后续还有一些基本的类函数就不再一一介绍了,代码中含有注释。
MemoryPool头文件
#ifndef MEMORY_POOL_H
#define MEMORY_POOL_H
#include <climits>
#include <cstddef>
//allocate 分配一个对象所需的内存空间
//
//deallocate 释放一个对象的内存(归还给内存池,不是给操作系统)
//
//construct 在已申请的内存空间上构造对象
//
//destroy 析构对象
//
//newElement 从内存池申请一个对象所需空间,并调用对象的构造函数
//
//deleteElement 析构对象,将内存空间归还给内存池
//
//allocateBlock 从操作系统申请一整块内存放入内存池
template <typename T, size_t BlockSize = 4096>
class MemoryPool
{
public:
/* Member types */
typedef T value_type;//值类型
typedef T* pointer;//指针类型
typedef T& reference;//非常量引用
typedef const T* const_pointer;//常量指针
typedef const T& const_reference;//常量引用
typedef size_t size_type;//无符号整型 unsigned int
typedef ptrdiff_t difference_type;//普通int类型
typedef std::false_type propagate_on_container_copy_assignment;
typedef std::true_type propagate_on_container_move_assignment;
typedef std::true_type propagate_on_container_swap;
template <typename U> struct rebind {
//结构体rebind
typedef MemoryPool<U> other;//将 Memory<U> 改名为 other
};
/* Member functions */
MemoryPool() noexcept;
MemoryPool(const MemoryPool& memoryPool) noexcept;
MemoryPool(MemoryPool&& memoryPool) noexcept;
template <class U> MemoryPool(const MemoryPool<U>& memoryPool) noexcept;
~MemoryPool() noexcept;
MemoryPool& operator=(const MemoryPool& memoryPool) = delete;//删除默认拷贝构造函数
MemoryPool& operator=(MemoryPool&& memoryPool) noexcept;//移动赋值函数
pointer address(reference x) const noexcept;//返回非常量引用类型变量地址
const_pointer address(const_reference x) const noexcept;//返回常量引用类型变量地址
// Can only allocate one object at a time. n and hint are ignored
//一次只能为一个目标分配空间 常量指针(const T* -> 不可以修改该地址存放的数据)
pointer allocate(size_type n = 1, const_pointer hint = 0);//均为可缺省参数
void deallocate(pointer p, size_type n = 1);//回收内存 归还给Block内存块
size_type max_size() const noexcept;//计算最大可使用的 Slot 槽
template <class U, class... Args> void construct(U* p, Args&&... args);
template <class U> void destroy(U* p);//调用析构函数释放 p 所指空间
template <class... Args> pointer newElement(Args&&... args);
void deleteElement(pointer p);
private:
union Slot_ {
value_type element;//使用时为 value_type 类型
Slot_* next;//需要回收时为 Slot_* 类型并加入 空闲链表中
};
typedef char* data_pointer_;//字符类型指针
typedef Slot_ slot_type_;//槽类型
typedef Slot_* slot_pointer_;//槽类型指针
slot_pointer_ currentBlock_;//指向第一块 Block 内存块
slot_pointer_ currentSlot_;//指向第一个可用元素 Slot 元素链表的头指针
slot_pointer_ lastSlot_;//可存放元素的最后指针
slot_pointer_ freeSlots_;//被释放的元素存放于空闲链表, freeSlots_为链表头指针
size_type padPointer(data_pointer_ p, size_type align) const noexcept;
void allocateBlock();//申请一块新的Block
//静态断言:当申请的BlockSize小于两个slot槽的内存时报错
static_assert(BlockSize >= 2 * sizeof(slot_type_), "BlockSize too small.");
};
#include "MemoryPool.tcc"
#endif // MEMORY_POOL_H
函数实现MemoryPool.tcc
#ifndef MEMORY_BLOCK_TCC
#define MEMORY_BLOCK_TCC
//计算对齐所需补的空间
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::padPointer(data_pointer_ p, size_type align)
const noexcept
{
uintptr_t result = reinterpret_cast<uintptr_t>(p);//占有的空间
return ((align - result) % align);//多余不够一个Slot槽大小的空间,需要跳过
}
//构造函数 初始化所有指针为 nullptr
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool()
noexcept
{
currentBlock_ = nullptr;//指向第一块Block区 即Block内存块链表的头指针
currentSlot_ = nullptr;//当前第一个可用槽的位置
lastSlot_ = nullptr;//最后可用Slot的位置
freeSlots_ = nullptr;//空闲链表头指针
}
//拷贝构造函数
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool& memoryPool)
noexcept :
MemoryPool()
{
}
//移动构造函数
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::MemoryPool(MemoryPool&& memoryPool)
noexcept
{
currentBlock_ = memoryPool.currentBlock_;
memoryPool.currentBlock_ = nullptr;
currentSlot_ = memoryPool.currentSlot_;
lastSlot_ = memoryPool.lastSlot_;
freeSlots_ = memoryPool.freeSlots;
}
//拷贝构造函数
template <typename T, size_t BlockSize>
template<class U>
MemoryPool<T, BlockSize>::MemoryPool(const MemoryPool<U>& memoryPool)
noexcept :
MemoryPool()
{
}
//移动赋值函数
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>&
MemoryPool<T, BlockSize>::operator=(MemoryPool&& memoryPool)
noexcept
{
if (this != &memoryPool)//若不是自己给自己赋值,进入if主体
{
std::swap(currentBlock_, memoryPool.currentBlock_);//交换第一块Block的位置
//swap函数内部使用的也是移动赋值机制,确保了不会有多个指针指向一块空间,这样就不会出现一块空间被重复释放的问题
currentSlot_ = memoryPool.currentSlot_;//其余指针赋值
lastSlot_ = memoryPool.lastSlot_;
freeSlots_ = memoryPool.freeSlots;
}
return *this;
}
template <typename T, size_t BlockSize>
MemoryPool<T, BlockSize>::~MemoryPool()//析构函数
noexcept
{
slot_pointer_ curr = currentBlock_;//指向第一块内存区block
while (curr != nullptr) {
slot_pointer_ prev = curr->next;//保存下一个槽的位置
operator delete(reinterpret_cast<void*>(curr));//释放空间 转为 void* 不需要调用析构函数
curr = prev;//往前走
}
}
//返回引用类型变量地址
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::address(reference x)
const noexcept
{
return &x;
}
//返回常量引用类型变量地址
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::const_pointer
MemoryPool<T, BlockSize>::address(const_reference x)
const noexcept
{
return &x;
}
//申请一块新的分区block
template <typename T, size_t BlockSize>
void
MemoryPool<T, BlockSize>::allocateBlock()
{
// Allocate space for the new block and store a pointer to the previous one
//申请 BlockSize字节大小的一块空间并用char* 类型指针接收
data_pointer_ newBlock = reinterpret_cast<data_pointer_>
(operator new(BlockSize));
//newBlock成为新的block内存首址
reinterpret_cast<slot_pointer_>(newBlock)->next = currentBlock_;
//currentBlock 更新为 newBlock的位置
currentBlock_ = reinterpret_cast<slot_pointer_>(newBlock);
// Pad block body to staisfy the alignment requirements for elements
//保留第一个slot 用于Block链表的链接
data_pointer_ body = newBlock + sizeof(slot_pointer_);
//求解空间对齐需要的字节数
size_type bodyPadding = padPointer(body, alignof(slot_type_));
//若出现残缺不可以作为一个slot的空间,跳过这块空间作为第一个可用slot的位置
currentSlot_ = reinterpret_cast<slot_pointer_>(body + bodyPadding);
lastSlot_ = reinterpret_cast<slot_pointer_>
(newBlock + BlockSize - sizeof(slot_type_) + 1);
//始址: newBlock 块大小 BlockSize 末址 newBlock + BlockSize 末址减去一个slot槽大小
//得到倒数第二个slot的末址 再加一得到最后一块slot的始址
}
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::allocate(size_type n, const_pointer hint)
{
//在内存池中申请 n 个Node,hint 默认设置为 空
if (freeSlots_ != nullptr) {
//空闲链表不为空,表示链表中有slot可以使用
pointer result = reinterpret_cast<pointer>(freeSlots_);//将头结点分配出去
freeSlots_ = freeSlots_->next;//头结点更新
return result;
}
else {
//空闲链表没有 slot
//如果当前可用slot槽已经到了最后的位置,用完之后需要再创建一块Block方便后续操作
if (currentSlot_ > lastSlot_)
allocateBlock();
return reinterpret_cast<pointer>(currentSlot_++);//返回分配的空间并更新 currentSlot
//currentSlot 指向Block内存块中最前面可用的Slot槽 (freeSlot链表中不算)
}
}
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deallocate(pointer p, size_type n)
{
if (p != nullptr) {
reinterpret_cast<slot_pointer_>(p)->next = freeSlots_;//union指针域被赋值 之前的数据销毁
freeSlots_ = reinterpret_cast<slot_pointer_>(p);//空闲链表头结点更新
}
}
//计算最大可用槽Slot的个数
template <typename T, size_t BlockSize>
inline typename MemoryPool<T, BlockSize>::size_type
MemoryPool<T, BlockSize>::max_size()
const noexcept
{
//无符号整型 unsiged int 类型运算,先将 -1 转换为无符号整型 即无符号整型最大值
size_type maxBlocks = -1 / BlockSize;//无符号类型数值可以表示的Block的个数
//unsigned int MAX / BlockSize 向下取整
return (BlockSize - sizeof(data_pointer_)) / sizeof(slot_type_) * maxBlocks;
// (BlockSize - sizeof(data_pointer)) / sizeof(slot_type_) →指每一块Block中可以存放的slot槽个数
// 最大可以有 maxBlocks 块内存块
//因此最大的可用槽个数 为每一块Block可以使用的Slot槽个数乘以最大可用Block
}
template <typename T, size_t BlockSize>
template <class U, class... Args>
inline void
MemoryPool<T, BlockSize>::construct(U* p, Args&&... args)
{
//完美转发 保存参数原有的属性 是左值还是左值 是右值还是右值
new (p) U (std::forward<Args>(args)...);
//placement new 的应用,即在已经申请的空间上申请内存分配
}
//调用析构函数
template <typename T, size_t BlockSize>
template <class U>
inline void
MemoryPool<T, BlockSize>::destroy(U* p)
{
//调用析构函数释放这一块内存
p->~U();
}
template <typename T, size_t BlockSize>
template <class... Args>
inline typename MemoryPool<T, BlockSize>::pointer
MemoryPool<T, BlockSize>::newElement(Args&&... args)
{
//为每个元素分配空间
pointer result = allocate();
construct<value_type>(result, std::forward<Args>(args)...);//参数包解包过程
return result;//返回每一个新申请空间的地址
}
//删除元素
template <typename T, size_t BlockSize>
inline void
MemoryPool<T, BlockSize>::deleteElement(pointer p)
{
// ↑传入 T* 类型的指针
if (p != nullptr) {
//判断指针为非空
p->~value_type();//调用值类型的析构函数 typedef T value_type;
deallocate(p);//将这块内存还给内存池
}
}
#endif // MEMORY_BLOCK_TCC
写在最后
笔者科班大二在学,初次接触项目,有很多还未完全理解透的地方,写这篇博客主要是记录成长的过程以及分享自己在反复揣摩源代码并查找大量资料最后的理解,存在表述不清或有误的地方还请指出,笔者将尽一切力量去修改纠正。文笔水平较低,各位大哥轻喷点哈哈哈哈(手动狗头)~~~
转载:https://blog.csdn.net/Genius_bin/article/details/116356478