首页 > 代码库 > 《Effective C++》内存管理
《Effective C++》内存管理
如果global new-hander没有成功配置,会抛出一个std::bad_alloc的exception。
#include<iostream> #include<new> #include<climits> using namespace std; template<class T> class NewHandlerSupport{ public: static new_handler set_new_handler(new_handler p); static void * operator new(size_t size); private: static new_handler currentHandler; }; template<class T> new_handler NewHandlerSupport<T>::set_new_handler(new_handler p) { new_handler oldHandler=currentHandler; currentHandler=p; return oldHandler; } template<class T> void * NewHandlerSupport<T>::operator new(size_t size) { new_handler globalHandler=std::set_new_handler(currentHandler); void *memory; try{ memory=::operator new(size); }catch(std::bad_alloc&){ std::set_new_handler(globalHandler); throw; } std::set_new_handler(globalHandler); return memory; } //以下使得每一个currentHandler为0 template<class T> new_handler NewHandlerSupport<T>::currentHandler; class X:public NewHandlerSupport<X> { private: int data; }; void noMoreMemory() { cerr<<"unable to satify the memory"<<endl; abort(); } int main() { X::set_new_handler(noMoreMemory); X *px1=new X; //如果内存配置失败,则调用noMoreMemory string *ps=new string;//如果内存配置失败,则调用global new_handler (如果有的话) X::set_new_handler(0);//设定专属的new-hander为NULL X *px2=new X;//如果内存配置失败,立刻抛出一个exception,因为此刻已没有登录“Class X专属的new-handler" }
说明:
知道1993年之前,c++还要求opertor new 在无法满足内存是传回0,现在的标准是抛出一个std::bad_alloc.为了以前的兼容,
c++继续提供”失败便传回0“的传统行为。这种形式称为nothrow形式。因为他们从不做任何抛出throw动作,而且他们在使用new的同时,使用了nothrow objects.(定义于标准头文件<new>之中)
class Widget { ... };
Widget *pw1 = new Widget; // throws bad_alloc if
// allocation fails
if (pw1 == 0) ... // this test must fail 这个测试一定会失败
Widget *pw2 =new (std::nothrow) Widget; // returns 0 if allocation for
// the Widget fails
if (pw2 == 0) ... // this test may succeed
在条款10中,作者写了memory pool。
让我们回过头去看看这样一个基本问题:为什么有必要写自己的operator new和operator delete?
答案通常是:为了效率。缺省的operator new和operator delete具有非常好的通用性,它的这种灵活性也使得在某些特定的场合下,可以进一步改善它的性能。尤其在那些需要动态分配大量的但很小的对象的应用程序里,情况更是如此。
例如有这样一个表示飞机的类:类airplane只包含一个指针,它指向的是飞机对象的实际描述(此技术在条款34进行说明):
class airplanerep { ... }; // 表示一个飞机对象
//
class airplane {
public:
...
private:
airplanerep *rep; // 指向实际描述
};
一个airplane对象并不大,它只包含一个指针(正如条款14和m24所说明的,如果airplane类声明了虚函数,会隐式包含第二个指针)。但当调用operator new来分配一个airplane对象时,得到的内存可能要比存储这个指针(或一对指针)所需要的要多。之所以会产生这种看起来很奇怪的行为,在于operator new和operator delete之间需要互相传递信息。
因为缺省版本的operator new是一种通用型的内存分配器,它必须可以分配任意大小的内存块。同样,operator delete也要可以释放任意大小的内存块。operator delete想弄清它要释放的内存有多大,就必须知道当初operator new分配的内存有多大。有一种常用的方法可以让operator new来告诉operator delete当初分配的内存大小是多少,就是在它所返回的内存里预先附带一些额外信息,用来指明被分配的内存块的大小。也就是说,当你写了下面的语句,
airplane *pa = new airplane;
你不会得到一块看起来象这样的内存块:
pa——> airplane对象的内存
而是得到象这样的内存块:
pa——> 内存块大小数据 + airplane对象的内存
对于象airplane这样很小的对象来说,这些额外的数据信息会使得动态分配对象时所需要的的内存的大小翻番(特别是类里没有虚拟函数的时候)。
如果软件运行在一个内存很宝贵的环境中,就承受不起这种奢侈的内存分配方案了。为airplane类专门写一个operator new,就可以利用每个airplane的大小都相等的特点,不必在每个分配的内存块上加上附带信息了。
具体来说,有这样一个方法来实现你的自定义的operator new:先让缺省operator new分配一些大块的原始内存,每块的大小都足以容纳很多个airplane对象。airplane对象的内存块就取自这些大的内存块。当前没被使用的内存块被组织成链表——称为自由链表free list——以备未来airplane使用。听起来好象每个对象都要承担一个next域的开销(用于支持链表),但不会:rep域的空间也被用来存储next指针(因为只是作为airplane对象来使用的内存块才需要rep指针;同样,只有没作为airplane对象使用的内存块才需要next指针),这可以用union来实现。
具体实现时,就要修改airplane的定义,从而支持自定义的内存管理。可以这么做:
class airplane { // 修改后的类 — 支持自定义的内存管理
public: //
static void * operator new(size_t size);
...
private:
union {
airplanerep *rep; // 用于被使用的对象
airplane *next; // 用于没被使用的(在自由链表中)对象
};
// 类的常量,指定一个大的内存块中放多少个
// airplane对象,在后面初始化
static const int block_size;
static airplane *headoffreelist;
};
上面的代码增加了的几个声明:一个operator new函数,一个联合(使得rep和next域占用同样的空间),一个常量(指定大内存块的大小),一个静态指针(跟踪自由链表的表头)。表头指针声明为静态成员很重要,因为整个类只有一个自由链表,而不是每个airplane对象都有。static非常重要。
#include<iostream> #include<new> #include<climits> #include<string> using namespace std; class AirPlaneRep{ public: string name; }; class AirPlane { public: static void * operator new(size_t size); static void operator delete(void *deadObject,size_t size); public: union{ AirPlaneRep *rep; AirPlane *next;//正对free list内的对象 }; static const int BLOCK_SIZE; static AirPlane *headOfFreeList; }; void * AirPlane::operator new(size_t size) { //如果大小错误,将内存配置申请转交给:operator new //详见条款8 if(size!=sizeof(AirPlane)) return ::operator new(size); AirPlane *p=headOfFreeList;//p指向free list的头部 //如果p是有效的,就把list的头部移往free list的下一个元素 if(p) headOfFreeList=p->next; else { //free list已空,配置一块够大的内存 AirPlane *newBlock=static_cast<AirPlane*>(::operator new(BLOCK_SIZE*sizeof(AirPlane))); //组成一个新的free list:将小块内存串接在一起 //跳过第0个元素,因为你要将他传回给operator new的调用者 for(int i=1;i<BLOCK_SIZE-1;i++) newBlock[i].next=&newBlock[i+1]; //以null指针作为整个linked list的结束 newBlock[BLOCK_SIZE-1].next=0; //将p设为list的头部,将headOfFreeList设为下一个可被运用的小块内存 p=newBlock; headOfFreeList=&newBlock[1]; } return p; } void AirPlane::operator delete(void *deadObject,size_t size) { if(deadObject==0) return;//见条款8 if(size!=sizeof(AirPlane))//见条款8 { ::operator delete(deadObject); return; } AirPlane *carcass=static_cast<AirPlane*>(deadObject); carcass->next=headOfFreeList; headOfFreeList=carcass; } AirPlane *AirPlane::headOfFreeList; const int AirPlane::BLOCK_SIZE=512; int main() { AirPlane *pa=new AirPlane(); AirPlaneRep *pr=pa->rep=new AirPlaneRep(); pr->name="jack"; AirPlane *pb=new AirPlane(); AirPlaneRep *pr2=pa->rep=new AirPlaneRep(); pr2->name="jack2"; cout<<"sizeof(AirPlane)"<<sizeof(AirPlane)<<endl; cout<<pr->name<<ends<<pr2->name<<endl; cout<<pa<<ends<<pb<<endl; }
输出:
sizeof(AirPlane)4
jack jack2
003C9588 003C958C
问:这里有memory leak吗?
答:不存在,memory leak问题是”在配置内存后,所有指向该内存的指针都遗失了“时才发生,如果缺乏垃圾收集系统(gc)或某种特殊的语言机制,这样的内存就没办法归还给系统。这里并没有内存泄露问题,因为并不会发生”所有指针都遗失“的情况。
如果将以上的内存配置管理方法,抽象出来成为一个独立的类,叫作 pool class,则对于不同的类与 pool 配置器搭配将获得这种内存配置功能:
接口如下:
class pool
{
public:
pool ( size_t n ) ; //配置出一个大小为 n objects 的内存空间
void * alloc ( size_t n ) ; //相当于上面的 operator new 的实现
void free ( void * p , size_t size ) ; //相当于上面的 operator delete 的实现
~pool ( ) ;
} ;
pool 将负责与自定义内存管理相关的工作,而 Airplane 将不再需要增加一些与 Airplane 本身无关的代码。
如果一个 pool 对象被产生, 则有一大块内存被创建;如果这个对象被销毁,它会释放它配置的所有内存。如今,Airplane 类可以通过聚合一个 pool 得到自定义的内存管理能力:
class airplane {
public:
... // 普通airplane功能
static void * operator new(size_t size);
static void operator delete(void *p, size_t size);
private:
airplanerep *rep; // 指向实际描述的指针
static pool mempool; // airplanes的内存池
};
inline void * airplane::operator new(size_t size)
{ return mempool.alloc(size); }
inline void airplane::operator delete(void *p,
size_t size)
{ mempool.free(p, size); }
// 为airplane对象创建一个内存池,
// 在类的实现文件里实现
pool airplane::mempool(sizeof(airplane));
这个设计比前面的要清楚、干净得多,因为airplane类不再和非airplane的代码混在一起。union,自由链表头指针,定义原始内存块大小的常量都不见了,它们都隐藏在它们应该呆的地方——pool类里。让写pool的程序员去操心内存管理的细节吧,你的工作只是让airplane类正常工作。
现在应该明白了,自定义的内存管理程序可以很好地改善程序的性能,而且它们可以封装在象pool这样的类里。但请不要忘记主要的一点,operator new和operator delete需要同时工作,那么你写了operator new,就也一定要写operator delete。
-
class Airplane
-
{
-
public:
-
static void * operator new ( size_t size ) ;
-
static void operator delete ( void * deadObject , size_t size ) ;
-
……
-
private:
-
AirplaneRep * rep ; //指向一个真实的 Airplane object
-
static pool mempool ;
-
} ;
-
-
// cpp
-
void * Airplane : : operator new ( size_t size )
-
{ return mempool . alloc ( size ) ; }
-
-
void Airplane : : operator delete ( void * deadObject , size_t size )
-
{ mempool . free ( deadObject , size ) ; }
书中没说怎么实现Pool。可以自己写下。
http://blog.sina.com.cn/s/blog_4b02b8d001007wdi.html
http://bbs.csdn.net/topics/80038013