6.2 new和delete运算符

运算符new的使用,事实上经过两个步骤完成:

int* pi = new int(5);

//1.通过new运算符,配置所需要的内存
int* pi =_new(sizeof(int));

//2.将配置的对象设立初值
*pi = 5; 

更进一步说,初始化操作应该再内存成功(经由new运算符)后才执行:

//new运算符的两个分离步骤
//int* pi = new int(5);

//重写声明
int* pi;
if(pi = _new(sizeof(int)))
	*pi = 5;

//delete的分离步走
//delete pi;

//重写声明
if(pi != 0)
	_delete(pi);

如果pi的值时0,C++语言会要求delete运算符不会执行,pi并不会自动清0,像下面这样的后继行为,虽然没有良好定义,但是可能(也可能不)被评估为真,这是因为对于pi所指之内存的变更或再使用,可能(也可能不)会发生。

if(pi && pi == 5) ...

pi所指对象的生命会因delete而结束,所以后继任何对pi的参考操作就不再包良好的行为,并因此被视为一种不好的程序风格。然而,把pi继续当一个指针来用,仍然可以的(虽然使用受到限制),例如:

//ok,pi仍然指向合法的空间
//甚至几十存储其中的object不再合法
if(pi == sentinel) ...

在这里,使用指针pi,和使用pi所指的对象,其差别在于哪一个生命已经结束了。虽然该地址的对象不再合法,地址本身却仍然代表一个合法的程序空间,因此pi能够继续使用,但只能在受限制的情况下使用,很想一个void*指针的情况。

以constructor来配置一个class object情况类似:

//被转化为
Point3d origin;
//C++ pseudo code
if(origin = _new(sizeof(Point3d)))
	origin = Point3d::Point3d(origin);

如果实现出exception handling,那么转换结果可能复杂些:

//C++ pseudo code
if(origin = _new(sizeof(Point3d)))
{
	try{
		origin = Point3d::Point3d(origin);
	}
	catch(...){
		//调用delete library funct释放内存
		_delete(origin);
		//将原来的exception上传
		throw;
	}
}

destructor应用类似:

delete origin;

//被内部转化
if(origin != 0)
{
	Point3d::~Point3d(origin);
	_delete(origin);
}

以下是library中new的实现方法(以下版本并没考虑exception handing):

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的调用都必须传回一个独一无二的指针,解决此问题的方法是传统的传回一个指针,指向一个默认为1byte的内存空间,另一个有趣之处是,它允许使用者提供一个属于自的_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 object所组成的数组的每一个元素上。倒是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数组的操作,只是单纯地获取内存和释放内存而已。

然而如果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。

对于写下如下的代码,析构函数对应的写法:

int array_size = 10;
Point3d* p_array = new Point3d[10];

//C++2.0之前必须这么写
delete [array_size] p_array;
				  
//C++2.0之后可以写么写
delete [] p_array;

为了回溯兼容,两种形式都可以接受。

对于如下的destructor操作而言:

delete p_array;

那么只有第一个元素被析构,其他元素仍然存在——虽然其相关的内存已经归还了。

Placement Operator new的语意

有一个预先定义好的重载的(overloaded)new运算符,成为placement operator new。它需要第二个参数,类型为void*,调用方式如下:

Poin2w* ptw = new(arena)Poin2w;	

其中arena指向内存中的一块区域,用以放置新产生出来的Point2w object。它的实现方法只要将“获得的指针(arena)”所指的地址传回即可:

void* operator new(size_t,void* p)
{
	return p;
}	

Placement new operator所扩充的另一半是将Point2w constructor自动实施于arena所指的地址上:

//C++ pseudo code
Poin2w* ptw = (Poin2w*)arena;
if(ptw != 0)
		ptw->Point2w::Point2w();

然而却有一个轻微不良行为:

//让arena成为全局性定义
vod foobar()
{
	Point2w* p2w = new(arena)Point2w;
	//do it
	//now manipulate a new object
	p2w = new(arena)Point2w;
}

如果placement operator在原已经存在的一个object上构造新的object,该object有一个destructor,这个destructor并不会被调用,调用该destructor的方法之一是将那个指针delete。不过在此例中如果这样做是错误的:

delete p2w;
p2w = new(arena)Point2w;

//施行destructor的正确方法
p2w->~Point2w;
p2w = new(arena)Point2w;

剩下的问题有一个是设计的问题:在例子中第placement operator第一次调用,会将新object构造于原已存在的object至上,还是会析构于全新地址上?我们如何知道arena所指的这块区域是否需要先析构?这个问题在语言层面没有解答,一个合理的习惯是指向new这一段也要负责执行destructor。

另一个问题关系到arena所表现的类型,C++ Standard说它必须指向相同类型的class,要不就是一块“新鲜”内存,足够容纳该类型的object。但是,derived class并不被支持。对于一个derived class,或是其他没有关联的类,其行为虽然并非不合法,却是未定义的。

新的内存空间可以这样配置:

char* arena = new char[sizeof(Point2w)];

相同类型的object可以这样获得:

Poin2w* arena = new Point2w;

不论哪种情况,新的Point2w的存储空间的确覆盖了arena的位置,此行为良好。然而,placement new operator并不支持多态。对于derived class比其base class大的话,derived class 的constructor会被破坏。

还有一个问题是:

struct Base{int j;virtual void f();};
struct Derived: Base{ void f();};				  

void fooBar()
{
		Base b;
		b.f();			//Base::f()被调用
		b.~Base();
		new(&b)Derived;
		b.f();		   //哪一个f()被调用
}

由于上述两个classes有相同的大小,把derived object放在base class而配置的内存中是安全的,然而,要支持这一点,就必须放弃动态的virtual function。尽管大部分使用者以为调用时Derived的,但大部分编译器调用却是Base。

猜你喜欢

转载自blog.csdn.net/weixin_28712713/article/details/84811056
今日推荐