首页 > 编程语言 > 详细

C++对象模型——new 和 delete 运算符(第六章)

时间:2015-08-17 01:10:19      阅读:304      评论:0      收藏:0      [点我收藏+]

6.2    new 和 delete 运算符

    运算符 new 的使用,看起来似乎是个单一运算,像这样:
int *pi = new int(5);
    但事实上它是由以下两个步骤完成:
    1.通过适当的 new 运算符函数实体,配置所需的内存:
// 调用函数库中的new运算符
int *pi = __new(sizeof(int));
    2.给配置得来的对象设立初值:
*pi = 5;
    更进一步地,初始化操作应该在内存配置成功(经由 new 运算符)后才执行:
// new运算符的两个分离步骤
// given: int *pi = new int(5);
// 重写声明
int *pi;
if (pi = __new(sizeof(int)))
    *pi = 5;
    delete 运算符的情况类似,当程序员写下:
delete pi;
    时,如果pi的值是0,C++语言会要求 delete 运算符不要有操作.因此,编译器必须为此调用构造一层保护膜:
if (pi != 0)
    __delete(pi);
    请注意pi并不会因此被自动清除为0,因此像这样的后继行为:
// 没有良好的定义,但是合法
if (pi && *pi == 5)
    ...
    虽然没有良好的定义,但是可能(也可能不)被评估为真.这是因为对于pi所指向的内存的变更或再使用,可能(也可能不)会发生.
    pi所指对象的生命会因 delete 而结束,所以后继任何对pi的参考操作就不再保证有良好的行为,并因此被视为是一种不好的程序风格.然而,把pi继续当做一个指针来用,仍然是可以的(虽然其使用受到限制),例如:
// pi仍然指向合法空间
// 甚至即使储存于其中的object已经不再合法
if (pi == sentine1)
    ...
    在这里,使用指针pi和使用pi所指的对象,其差别在于哪一个的声明已经结束了.虽然该地址上的对象不再合法,但地址本身却仍然代表一个合法的程序空间.因此pi能够继续被使用,但只能在受限制的情况下,很像一个 void * 指针的情况.
    以constructor来配置一个 class object,情况类似,例如:
Point3d *origin = new Point3d;
    被转换为:
Point3d *origin;
if (origin = __new(sizeof(Point3d)))
    origin = Point3d::Point3d(origin);
    如果exception handling的情况下,destructor应该被放在一个try区段中.exception handler会调用 delete 运算符,然后再一次丢出该exception.
    一般的lirary对于 new 运算符的实现操作都是很直接了当,但有两个精巧之处值得斟酌:
extern void *operator new(size_t size) {
    if (size == 0)
        size = 1;
    void *last_alloc;
    while (!(last_alloc = malloc(size))) {
        if (_new_handler)
            (*_new_handler)();
        else
            return 0;
    }
    return last_alloc;
}
    虽然这样写是合法的:
new T[0];
    但是语言要求每一次对 new 的调用都必须传回一个独一无二的指针.解决该问题的传统方法是传回一个指针,指向一个默认为1 byte的内存区块(这就是为什么程序代码中的size被设为1的原因).这个实现技术的另一个有趣之处是,它允许使用者提供一个属于自己的_new_handler()函数.这正是为什么每一次循环都调用_new_handler()的缘故.
    new 运算符实际上总是以标准C malloc()完成,虽然并没有规定一定这么做不可.相同的情况,delete 运算符也总是以标准的C free()完成:
extern void operator delete(void *ptr) {
    if (ptr)
        free((char *)ptr);
}

针对数组的 new 语意

    当这么写:
int *p_array = new int[5];
    时,vec_new()不会真正被调用,因为它的主要功能是把default constructor施行于 class objects所组成的数组的每一个元素上.倒是 new 运算符函数会被调用:
int *p_array = (int *)__new(5 * sizeof(int));
    相同的情况,如果写:
// struct simple_aggr {float f1, f2; };
simple_aggr *p_aggr = new simple_aggr[5];
    vec_new()也不会被调用.为什么呢?因为simple_aggr并没有定义一个constructor或destructor,所以配置数组以及清除p_aggr数组的操作,只是单纯地获得内存和释放内存而已.这些操作由 new 和 delete 运算符来完成就绰绰有余了.
    然而如果 class 定义有一个default constructor,某些版本的vec_new()就会被调用,配置并构造 class objects所组成的数组,例如这个算式:
Point3d *p_array = new Point3d[10];
    通常会被编译为:
Point3d *p_array;
p_array = vec_new(0, sizeof(Point3d), 10, &Point3d::Point3d, &Point3d::~Point3d);
    在个别的数组元素构造过程中,如果发生exception,destructor就胡被传递给vec_new().只有已经构造妥当的元素才需要destructor的施行,因为它们的内存已经被配置出来了,vec_new()有责任在exception发生的时候把那些内存释放掉.
    在 delete 时不需要指定数组元素的数目,如下所示:
delete []p_array;
    寻找数组维度给 delete 运算符的效率带来极大的影响,所以才导致这样的妥协:只有在中括号出现时,编译器才寻找数组的维度,否则它便假设只有单独一个object要被删除.如果程序员没有提供必须的中括号,像这样:
delete p_array;
    那么就只有第一个元素会被解构,其他元素仍然存在.
    应该如何记录元素数目?一个明显的方法就是为vec_new()所传回的每一个内存区块配置一个额外的word,然后把元素数目包藏在那个word中.通常这种被包藏的数值称为所谓的cookie.然而Sun编译器却维护一个"联合数组",放置指针以及大小.它也把destructor的地址维护于此数组中.
    cookie策略有一个普遍引起忧虑的话题,那就是如果一个坏指针被交给delete_vec(),取出来的cookie自然是不合法的.一个不合法的元素数目和一个坏的起始地址,会导致destructor以非预期的次数被施行于一段非预期的区域,然而在"联合数组"的政策下,坏指针的可能结果就只是取出错误的元素数目而已.


版权声明:本文为博主原创文章,未经博主允许不得转载。

C++对象模型——new 和 delete 运算符(第六章)

原文:http://blog.csdn.net/yiranant/article/details/47708929

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!