《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

时间: 2024-12-11 22:52:00

《Effective C++》内存管理的相关文章

effective OC2.0 52阅读笔记(五 内存管理)

第五章:内存管理 29 理解引用计数 30 以ARC简化引用计数 总结:ARC通过命名约定将内存管理规则标准化.其他编程语言很少像OC这样强调命名.ARC通过设置全局数据结构(此数据结构的具体内容因处理器而异)中的一个标志位,来代替直接调用autorelease和retain.这是ARC所带来的好处.待编译器与运行期组件日臻成熟,还会出现其他的优化技术.CoreFoundation对象不归ARC管理,开发者必须适时调用CFRetain/CFRelease. 31 在dealloc方法中只释放引用

[自制简单操作系统] 3、内存管理和窗口叠加

1.本次主要进展 >_<" 这次主要学习了系统内存管理和窗口叠加~由于上两篇都做了详细的框架说明和介绍,这里直接上代码! 2.文件及函数构成 >_<" 这里和第二篇相比,把鼠标和键盘的相关函数独立出来放进各自相应的文件中,并主要在内存管理和窗口叠加进行探索,同时还有部分代码整理~ 1 /* In this file, not only have the defination of the function, but also 2 hava the descrip

&lt;Linux内核源码&gt;内存管理模型

题外语:本人对linux内核的了解尚浅,如果有差池欢迎指正,也欢迎提问交流! 首先要理解一下每一个进程是如何维护自己独立的寻址空间的,我的电脑里呢是8G内存空间.了解过的朋友应该都知道这是虚拟内存技术解决的这个问题,然而再linux中具体是怎样的模型解决的操作系统的这个设计需求的呢,让我们从linux源码的片段开始看吧!(以下内核源码均来自fedora21 64位系统的fc-3.19.3版本内核) <include/linux/mm_type.h>中对于物理页面的定义struct page,也

Objective-C:内存管理

1 传统内存管理 Objective-C对象的生命周期可以分为:创建.存在.消亡. 1.1 引用计数 类似Java,Objective-C采用引用计算(reference counting)技术来管理对象的生命周期.每个对象都定义有一个整数(称引用计数器)与之相关联,该数用以表示当前有多少个指针指向该对象. 1.1.1 操作方法 当某段代码需要访问一个对象时,该代码就将对象的保留计数值加1:当结束访问时就减1:若引用计数器减到0时,该对象将被销毁.引用计数器的值由如下三种操作进行控制: 创建 当

C++内存管理学习笔记(2)

/****************************************************************/ /*            学习是合作和分享式的! /* Author:Atlas                    Email:[email protected] /*  转载请注明本文出处: *   http://blog.csdn.net/wdzxl198/article/details/9059883 /************************

c++内存管理学习纲要

本系列文章,主要是学习c++内存管理这一块的学习笔记. 时间:6.7-21 之下以技术内幕的开头语,带入到学习C++内存管理的技术中吧: 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,因此要想成为C++高手,内存管理一关是必须要过的! 笔记汇总: 1.C++内存管理学习笔记(1) 2.C++内存管理学习笔记(2) 3.C++内存管理学习笔记(3) 4.C++内存管理学习笔记(4) 5.C++内存管理学习笔记(5) 6.C++内存管理学习笔记(6) 7.C++内存管理学习笔记(7

Erlang 虚拟机内的内存管理(Lukas Larsson演讲听写稿)

Erlang核心开发者Lukas Larsson在2014年3月份Erlang Factory上的一个演讲详细介绍了Erlang内存体系的原理以及调优案例: http://www.erlang-factory.com/conference/show/conference-6/home/#lukas-larsson 在这里可以下载slides和观看视频(为了方便不方便科学上网的同学,我把视频搬运到了 http://v.youku.com/v_show/id_XNzA1MjA0OTEy.html )

酒逢知己,雨后甘霖---C++之内存管理

特别警告:本文非本人所写,但阅读后真的受益匪浅,内心有激昂澎湃之势,特以"原创"之由分享于此,感谢原作者的无私奉献!由于最近在研究<Effective C++>等书籍的原因,真是发自内心的感受到了C++的博大精深,以至与作者共鸣,特记于此! 原文链接:http://blog.csdn.net/zhaozhao531322/article/details/13628963 [导语] 内存管理是C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能

揭开Java内存管理的面纱

前言 相对于C.C++这些高性能语言,Java有着让此类程序员羡慕的功能:内存自动管理.似乎这样,Java程序员不用再关心内存,也不用去了解相关知识.但结果真的是这样吗?特别对于我们这种Android程序员来说,对内存可是吃得死死的,一旦出现较为复杂的内存泄露和溢出方面的问题,简直就是噩梦.因此,对Java内存管理有个大体的了解似乎已经成为一个合格的Android程序员必备的技能,就算是新进的Kotlin同样是基于JVM的.不如趁此机会,大家一起来揭开它的面纱. 对象 Java是一门面向对象的编