为什么有必要写自己的operator new和operator delete?
为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。
当调用operator new来分配对象时,得到的内存可能要比存储这个对象所需的要多。因为operator new和operator delete之间需要传递信息。确幸版本的operator new是一种通用型的内存分配器,他必须能够分配任意大小的内存块。operator delete也要可以释放任意大小的内存块。operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。一种常用的方法就是让operator new来告诉operator delete当初分配的内存大小是多少,就是在它返回的内存里预带一些额外信息,用来指明被分配的内存块的大小。
在类内实现的内存池
如果软件运行在一个内存很宝贵的环境中,可以专门实现一个operator new,分配一大块内存,再将其分割给链表,new时分配一个节点,delete时再将这个节点放回到链表中。
#pragma once
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using std::string;
class AirPlaneRep
{
public:
AirPlaneRep();
~AirPlaneRep();
private:
int size;//随便写几个成员
string name;
double value;
};
AirPlaneRep::AirPlaneRep()
{
}
AirPlaneRep::~AirPlaneRep()
{
}
class AirPlane
{
public:
AirPlane();
static void* operator new(size_t size);
static void operator delete(void* deadObj);
~AirPlane() {};
private:
union
{
AirPlaneRep* rep;//指向实际对象
AirPlane* next;//用于没被使用的对象(放在链表中时指向下一个内存块)
};
//指定申请的一大块内存可以放几个对象
static const int BLOCK_SIZE = 512;
static AirPlane * head_of_list;//静态指针指向分配的一大块内存,不用在析构函数里释放,静态类型的生存周期结束时,自动释放
};
AirPlane * AirPlane::head_of_list;//静态成员的声明不能带static
AirPlane::AirPlane()
{
cout << "进入构造" << endl;
}
void * AirPlane::operator new(size_t size)
{
//把"错误"大小的请求转给::operator new(),比如派生类可能会调用到这个函数
if (size != sizeof(AirPlane))
{
return ::operator new(size);
}
AirPlane* p = head_of_list;//第一次为NULL,跳到else分配指定大小的内存
//如果p可用,说明已经分配过空间了
if (p)
{
cout<<"使用一个链表节点"<<endl;
head_of_list = p->next;
//return p;
}
else
{
cout << "第一次使用,分配一大块内存" << endl;
//自由链表为空,则使用全局operator new分配一个大的内存块
//如果申请失败,则在此处调用set_new_handler机制
AirPlane * newBlock = static_cast<AirPlane*>(::operator new(BLOCK_SIZE * sizeof(AirPlane)));
//接下来将内存块配置成一个自由链表
//跳过第一个小内存块,因为它要被返回
for (int i = 1; i < BLOCK_SIZE; i++)
{
newBlock[i].next = &newBlock[i + 1];
}
//用空指针结束链表
newBlock[BLOCK_SIZE - 1].next = NULL;
p = newBlock;
head_of_list = &newBlock[1];
}
return p;
}
inline void AirPlane::operator delete(void * deadObj)
{
cout << "进入自定义delete" << endl;
if (deadObj == NULL)
return;
if (sizeof(*(static_cast<AirPlane*>(deadObj))) != sizeof(AirPlane))
{
cout << "不是基类airplane类型,调用全局delete对象!" << endl;
::operator delete(deadObj);
return;
}
AirPlane *dead = static_cast<AirPlane*>(deadObj);
//将其放在头节点前边,作为头节点,就是放回内存池,不是真的释放空间
cout << "放回内存池" << endl;
dead->next = head_of_list;
head_of_list = dead;
}
测试代码:
// AirPlane.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include "AirPlane.h"
int main()
{
std::cout << "Hello World!\n";
AirPlane *a1 = new AirPlane();
AirPlane *a2 = new AirPlane();
delete a1;
cout << "TEST END!!!" << endl;
cout << "退出程序之时,静态变量生存期结束,释放申请的内存池" << endl;
}
// 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
// 调试程序: F5 或调试 >“开始调试”菜单
// 入门提示:
// 1. 使用解决方案资源管理器窗口添加/管理文件
// 2. 使用团队资源管理器窗口连接到源代码管理
// 3. 使用输出窗口查看生成输出和其他消息
// 4. 使用错误列表窗口查看错误
// 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
// 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
测试结果:
可以看到,首先分配内存,再构造对象。且delete对象时,内存放回了内存池中。
上述内存池内嵌在了airplane类中,为了提高代码重用性,可以写一个简单的内存池分配器类。需要的类直接包含该类即可。
简单实现内存池分配器类
此处使用vector<T*>来实现一个简单的内存池,首先将申请的T大小的空间的指针放在vector中,需要时弹栈一个,不需要时放回vector中。
因为此处存放申请到的内存的不是静态指针,所以需要在析构函数中释放掉。
同时为了看一下内存池耗尽情况,将默认内存池大小修改为2。
#pragma once
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
class Pool
{
public:
//构造大小为n个T的内存池对象
Pool(int num = 2 ,int up = 1) : up_attr(up), block_size(num)
{
init_mem(block_size);
}
void init_mem(int num)
{
try
{
int i = 1;
for (; i <= num; i++)
{
T * tmp = static_cast<T *>(malloc(sizeof(T)));
mempool.push_back(tmp);
}
}
catch (const std::bad_alloc&)
{
cout << "内存分配失败" << endl;
exit(-1);
}
}
void get_more_mem()
{
init_mem(block_size * 2 + up_attr);
block_size += block_size * 2 + up_attr;
++up_attr;
}
//为一个对象分配足够的内存
T * alloc()
{
cout << "内存池分配一个对象所需空间" << endl;
if (mempool.size()==0)
{
cout<<"内存池耗尽,需再次申请内存空间"<<endl;
get_more_mem();
}
T* tmp = mempool.back();
mempool.pop_back();
return tmp;
}
//将p所指的内存返回到内存池中
void free(void* p)
{
p = NULL;
mempool.push_back(static_cast<T*>(p));
}
//释放内存池中全部内存
~Pool()
{
for (int i = 0; i < mempool.size(); i++)
{
::free(mempool[i]);
}
}
//验证使用函数
int get_block_size()
{
return block_size;
}
//验证使用函数
int size()
{
return mempool.size();
}
private:
int block_size;
int up_attr;//放大因子
vector<T*> mempool;
};
此时,再去实现上面那个类,就比较简单了,只需要调用内存池类的方法即可。
使用这个内存池
#pragma once
#include "Pool.h"
class AirPlaneRep
{
public:
AirPlaneRep(){}
~AirPlaneRep(){}
private:
string name;
};
class AirPlane
{
public:
AirPlane() {};
~AirPlane() {};
static void* operator new(size_t size);
static void operator delete(void *p);
//验证需要放在public中(或写一个公有函数返回它),否则无法访问,实际应该放在private中
static Pool<AirPlane> my_pool;
private:
//static Pool<AirPlane> my_pool;
AirPlaneRep* rep;//指向实际对象
};
Pool<AirPlane> AirPlane::my_pool;
void* AirPlane::operator new(size_t size)
{
return my_pool.alloc();
}
inline void AirPlane::operator delete(void * p)
{
cout << "释放对象(将其所用空间放回到内存池中)" << endl;
my_pool.free(p);
}
验证:
// Pool.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "pch.h"
#include <iostream>
#include <string>
#include "AirPlane.h"
int main()
{
std::cout << "Hello World!\n";
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size()<<"个T" << endl;
AirPlane *a1 = new AirPlane;
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size() << "个T" << endl;
AirPlane *a2 = new AirPlane;
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size() << "个T" << endl;
AirPlane *a3 = new AirPlane;
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size() << "个T" << endl;
delete a1;
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size() << "个T" << endl;
delete a2;
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size() << "个T" << endl;
delete a3;
cout << "内存池空间:" << AirPlane::my_pool.get_block_size() << "个T" << endl;
cout << "内存池剩余空间" << AirPlane::my_pool.size() << "个T" << endl;
}
验证结果:
总结
基本符合预期,使用、放回、耗尽情况下都得到了验证,完成了内存池的基本功能。后续抽时间会写一个复杂的、功能更完备的内存池。