为了效率。缺省的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; };此时,再去实现上面那个类,就比较简单了,只需要调用内存池类的方法即可。
基本符合预期,使用、放回、耗尽情况下都得到了验证,完成了内存池的基本功能。后续抽时间会写一个复杂的、功能更完备的内存池。
