一、普通new运算符和delete运算符
1.new运算符实际上由两个步骤组成:
①分配所需的内存:通过调用适当库的new运算符函数来分配内存(实际上所有new运算符都是由malloc完成,自己重载new时也应该用malloc分配内存,delete都由free完成)
②在分配的内存上建立对象或初始化(内置类型)
int *pi=new int(5); //分解为以下两个步骤 int *pi=_new(sizeof(int)); //分配内存 *pi=5; //设置初始值
2.delete运算符需要保证删除的内存有效
delete pi; //转换为: if(!pi) _delete(pi);
3.delete只是清除内存中的对象,但是指针本身依然有效,依然指向一个合法的地址,类似于void*指针。
4.当需要以构造函数来配置对象时并且引入异常处理机制时,new和delete复杂一些
Point3d *origin=new Point3d; //转换为 Point3d *origin; //C++伪码 if(origin=_new(sizeof(Point3d))){ try{ origin=Point3d::Point3d(origin); } catch(...){ _delete(origin); //删除已经分配的内存; throw; //抛出原来的错误 delete origin; //转换为 if(origin!=0){ try{ Point3d::~Point3d(origin);} catch{...} { _delete(origin); throw; }}
5.一般的library中的new操作符会在传入需要地址大小的参数size为空时,自动申请一个大小为1的地址空间并返回(为了保证能返回一个有效地址)并且还能让使用者提供一个自己的处理函数new_handle().
注:书上没讲具体操作,只是写了伪码,C++primer上说如果程序员要实现内存分配和构造对象分离的操作,那么内存分配通过new运算符实现(可以自己重载new运算符通过molloc函数),对象构建则通过定位new实现)。
二、针对数组的new和delete
1.如果创建一个元素不含construct的数组,则不会调用vec_new(),因为只需要单纯的分配和释放内存。
2.如果创建的数组其元素包含构造函数和析构函数则会调用vec_new()和vec_delete().
int *p_array=new int[5]; //转换如下 int *p_array=(int*)_new(5*sizeof(int)); Point3d *p_array=new Point3d[10]; //通常被编译为 Point3d *p_array; p_array=vec_new(0,sizeof(Point3d),10,&Point3d::Point3d,&Point3d::~Point3d);
3.删除一个数组的元素现在的做法是直接调用 delete[] p_array 不用在【】中加入数组大小(以前需要加入,现在加入数组大小被视为不良的做法)。
因为现在的编译器①在new申请的内存区块中配置一个额外的word用来存放包含元素个数的包 或者 ②维护一个联合数组放置指针及大小。
情况1当一个坏指针被交给delete_vec,会导致一个不正确的元素个数和起始地址(因为包改变了原本内存大小,所以一个错误的指针也没法指向正确的开头)。
情况2当传入一个坏指针时,最多造成元素个数的错误。
4.不要将一个基类指针指向派生类数组,否则就需要遍历数组自行进行析构函数的调用。
Point *ptr=new Point3d[10]; //调用自动的删除函数 delete [] ptr; //此时传入vec_delete()函数的参数是Point的析构函数,其大小和Point3d不同,所以删除地址时,只有第一个元素的Point子对象被正确的清除。 //需要程序员手动清除 for(int ix=0;ix<elem_count;++ix) { Point3d* p=&((Point3d*)ptr)[ix]; delete p; }