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

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以非预期的次数被施行于一段非预期的区域,然而在"联合数组"的政策下,坏指针的可能结果就只是取出错误的元素数目而已.

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

时间: 2024-10-08 13:29:32

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

重载类的new和delete运算符成员函数

重载类的new和delete运算符成员函数1. 调用new时,先分配内存,后调用构造函数.调用构造函数的行为由编译器控制.2. 调用delete时,先调用析构函数,后释放内存.调用析构函数的行为由编译器控制.重载这两个运算符函数的目的是为了控制内存的分配与释放.如果需要对某个类型频繁地创建和销毁大量的对象,new和delete运算过程可能会耗费过多的时间,并且会产生过多的内存碎片.这两个运算符函数的原型:void * operator new(size_t sz);void operator d

C++学习32 重载new和delete运算符

内存管理运算符 new.new[].delete 和 delete[] 也可以进行重载,其重载形式既可以是类的成员函数,也可以是全局函数.一般情况下,内建的内存管理运算符就够用了,只有在需要自己管理内存时才会重载. 重载 new 有两种形式: //以类的成员函数的形式进行重载 void * 类名::operator new ( size_t size ){ //TODO: } 和 //以全局函数的形式进行重载 void * operator new ( size_t size ){ //TODO

用new和delete运算符进行动态分配和撤销存储空间

测试描述:临时开辟一个存储空间以存放一个结构体数据 #include <iostream> #include <string> using namespace std; struct Student { string name; int age; char sex; }; int main() { Student *p; //定义指向结构体类型Student的数据的指针变量p p=new Student; //用new运算符开辟一个存放Student型数据的空间,把地址赋给p p-

c/c++ 重载new,delete运算符 placement new

重载new,delete运算符 new,delete在c++中也被归为运算符,所以可以重载它们. new的行为: 先开辟内存空间 再调用类的构造函数 开辟内存空间的部分,可以被重载. delete的行为: 先调用类的析构函数 再释放内存空间 释放内存空间的部分,可以被重载. 为什么要要重载它们? 有时需要实现内存池的时候需要重载它们.频繁的new和delete对象,会造成内存碎片,内存不足等问题,影响程序的正常执行,所以一次开辟一个适当大的空间,每次需要对象的时候,不再需要去开辟内存空间,只需要

语法》第六章 数组

(本文为阮一峰js标准教程的学习笔记,旨在总结该教程中涉及的知识点大纲及个人所做的一些拓展,方便作为"目录"或者"大纲"复习和查漏补缺,详细内容请参见阮一峰教程原文) 第二部分 语法 *********第六章 数组*********** 一.数组的定义1.概念:按次序排列的一组数,每个值都有编号(从0开始)整个数组用[]表示2.可以定义时赋值,也可定以后赋值.arr[0]='a';3.任何数据类型都可放入数组,[1,'1',[1,2],{},function(){}

ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第六章:管理产品图片:多对多关系(上)

这章介绍了怎样创建一个新的实体来管理图片,怎样使用HTML窗体来上传图片文件和使用多对多关系来使它们与产品相关,并且怎样来保存图片到文件系统中.这章也介绍了更多复杂的错误处理增加客户端错误到模型中为了把它们显示回给用户.在这章中播种数据库使用的产品图片可能在在第六章的从Apress网页站点下载代码中. 注意:如果你想遵从这章的代码,你必须完成第五章的代码或者从www.apress.com下载第五章的源代码作为一个起点. 创建实体保存图片文件名 这个项目,我们正要使用文件系统在Web项目中存储图片

Linux与云计算——第二阶段Linux服务器架设 第六章:目录Directory服务器架设—FreeIPA

Linux与云计算--第二阶段Linux服务器架设 第六章:目录Directory服务器架设-FreeIPA 1 FreeIPA 配置FreeIPA服务器 Configure IPA Server to share users' account in your local network. [1] Install FreeIPA. [[email protected] ~]# yum -y install ipa-server ipa-server-dns bind bind-dyndb-lda

C语言笔记(谭版 第六章~末)

第六章       利用数组批量处理数据 数组是一组有序数据的集合,数组中的每一个元素都属于同一数据类型. 算是一种数据的组织结构.初现结构端倪. 一维数组 定义: 类型符 数组名[常量表达式]  数组名命名遵行标示符命名规则 C语言中不允许对数组的大小作动态定义. 说明:如果在被调用函数中定义数组,其长度可以是变量或非常量表达式.实参.此情况称为可变长数组.若数组指定为静态存储方式,此方式不合法. 引用: 数组名[下标] 下标是常量或常量表达式   序号从0开始计起. 初始化:初始化列表  i

锋利jQuery 学习整理之 第六章 jQuery 与Ajax 的应用

1.Ajax 的XMLHttpRequest 对象 XMLHttpRequest 是Ajax 的核心,它是Ajax 实现的关键---发送异步请求.接受响应及执行回调都是通过它来完成的.XMLHttpRequest最早是在Microsoft Internet Explorer  5.0  ActiveX 组件中被引用的. 2.JQuery 中的Ajax jQuery 对Ajax 进行了封装,在jQuery中$.ajax()方法属于最底层的方法,第二层是load().$.load()和$.post(