Chinaunix首页 | 论坛 | 博客
  • 博客访问: 373813
  • 博文数量: 38
  • 博客积分: 256
  • 博客等级: 入伍新兵
  • 技术积分: 846
  • 用 户 组: 普通用户
  • 注册时间: 2012-12-14 23:21
文章分类

全部博文(38)

文章存档

2015年(1)

2014年(1)

2013年(28)

2012年(8)

我的朋友

分类: C/C++

2013-01-07 00:23:09

关于我们为什么要重载new和delete,我想看过我前一基础篇的朋友,应该都知道了。最基本的原因就是我们想控制内存分配的过程。

如果你要问更详细的条条框框,来解释重载new/delete可以做什么的话,我推荐你去读读《Effective C++》条款50。

这里我不想描述这些书本上已经有的东西,而是想记录一些我实战中的经验。

重载new和delete需要注意些什么 a. 我们知道,内存分配在C中是由两个函数实现的,而在C++中是由两个操作符实现的。所以在我们重载new和delete的时候就应该遵守一定的编程规则:
  • 不要分配一个size为0的内存,因为C++要在你分配的这块内存上执行constructor,所以如果你这样做,你的程序会崩溃;
  • delete一个NULL指针的时候,应该立即返回成功,你不要指望用户会做NULL判断,在把要delete的指针传给你;
  • 在new的时候,你要决定,是否要处理内存不足的情况,如果这样,你应该自己设定一个new-handler;
b. 重载了new,就应该重载一个对应的delete,即使这个delete仅仅是作为系统delete的一个代理。这样可以避免我们犯错误。特别是在我们重载一个placement new时。

从“基础”篇,我们知道,new一个对象包含两个过程,一是分配内存,二是执行对象的constructor。如果constructor执行失败的话(抛出了异常,比如在constructor里面,我们还有一个内存分配,但是没得到),编译器会帮我们调用对应的delete去释放分配得到的内存。什么是对应的delete?就是有相同的signature。

比如:void * operator new (size_t size, Pool *memPool),  它对应的delete就是 void operator delete(void *p, Pool *memPool)。

这里要注意的是,这种情况只发生在new的过程中,在真正的delete时,你可以调用系统默认的delete。所以编译器不会报错,如果你没有实现一个对应的delete,因为在你的代码中,你是用系统提供的delete操作符进行内存回收的。

c. 大部分情况下,我们重载的都是C++默认的,也就是那个抛出异常的new。这里要注意的就是,这个new在G++中,不允许你返回一个NULL(在Visual C++中,你可以)。这意味着什么呢?

还记得我们提到过的,编译器会为我们在new后面添加代码来调用对象的constructor吗?对的,G++之所以不允许你返回NULL,是因为这是个抛出异常的new,你只能通过异常来通知用户,出现了错误,如果你返回NULL,编译器添加的代码就会在0地址去执行constructor,这样......

为什么在Visual C++中,我们可以呢?这是因为Visual C++添加的代码中,包含了一个NULL 判断,如果是NULL, 它就跳过constructor,而返回这个NULL 到客户那里。

你可定会问,我们都不用异常,那怎么办?有没有解决方案?别急,当然有,如果没有,那g++不就是太挫了?你只要在你的编译参数中加入 -fcheck-new 就可以了。

从这里你可以看到,虽然C++提供了不抛出异常的new,但是它并不鼓励我们用它。相反,它让抛出异常的new包含了这部分功能。这就是为什么,在现实中,我们很少看到有人用C style的new。

d. 有没有试过对一个delete过的对象,再调用它的虚函数,比如:
  1. class A {

  2. public:

  3. A() { cout << "A constructor." << endl; }

  4. virtual ~A() { cout << "A destructor." << endl; }

  5. virtual void test() { cout <<


 

猜猜结果........................当然是segment fault :)

为什么?因为delete后,pB的vtable指针已经被设置成NULL了。那么是谁设置的呢?我们的destructor没这个动作,那么肯定是operator delete做的了。

是不是呢?

好的,我们来试试这个类:

  1. class A {

  2. public:

  3. A() { cout << "A constructor." << endl; }

  4. virtual ~A() { cout << "A destructor." << endl; }

  5. virtual void test() { cout <<

 

再试试....嗯,你可以看见,你成功了,程序正常结束。这就是野指针的危害,也是为什么,我们总是被教导,把delete过的指针设置为NULL。

各位看看还有什么问题。嗯,在多线程程序中,把delete过的指针设置成NULL,并不能解决所有问题。

那我们应该怎么做?

这里有见很好玩的事情,就是你delete一个对象两次,那个对象的destructor并不会被调用两次。第二次调用会在第一次调用结束后也相应的结束了,它什么也不做。

所有这里就提醒我们,在destructor中,把constructor中设置的初始值都reset掉。这样会让我们的程序更加健壮。

你们可以自己写代码试试。这里所有的秘密都是基于我在基础篇写的东西。

 

e. 你在父类中重载了new和delete,子类中还要做类似的事情吗?

其实这是个函数范围的问题。类也是一个scope(范围),一个继承体系也会继承类的范围,特别是public的函数,包括static函数。

所以,如果子类和父类的new/delete做同样的事情,那么你不需要再写一遍。

 

累了,今天就写到这里,下一篇,我将给大家介绍一个设计模式,它可以帮助我们为一个memory pool 设计一个接口,以方便用户使用。

阅读(1558) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~