C++中运算符new的深入讲解

目录

  • 一 new运算符语法
  • 二 new 如何工作(理解运算符new 和函数operator new)
  • 三 operator new函数
  • 四 一个综合性的例子
  • 五 使用new可能导致的内存泄漏

写这篇文章的原因是:有一次,我见到了类似下面的代码,我感到很惊奇,从来没见到new的这种用法,后来查看MSDN,发现一个常用的new运算符居然有这么多语法规定,甚是感慨!所以记录在此,日后备查!

 Blanks *pBlanks = new(0xa5) Blanks;

一 new运算符语法

new运算符负责从堆内存区分配对象或者对象数组的内存空间,返回一个指向对象的非0指针。其语法为

[::] new [placement] new-type-name [new-initializer]
[::] new [placement] ( type-name ) [new-initializer]

其中:
placement:占位符,当重写operator new时,提供了一个传递额外参数的方式;占位符可以包含多个参数。
type-name:分配内存的类型,可以是内置的或者类类型。
initializer:对象的初始化值。不能用于数组。只有类具有默认构造函数的时候,new 运算符才会创建这个类的数组。
上述就是new操作符的基本语法,然后先把它放在一边。看一下操作符new如何工作。

二 new 如何工作(理解运算符new 和函数operator new)

分配运算表达式(即包含new操作符的表达式)会做三件事情:

  1. 当分配一个或多个对象时,定位并保留内存空间。这个阶段完成后,就分配了正确数量的内存空间,但这还不是一个对象。
  2. 初始化对象。一旦初始化完成,在内存空间就有了足够的信息,使其成为一个对象。
  3. 返回一个继承自此对象的指针。程序可以通过这个指针访问新建的对象。

new运算符会调用函数operator new. 对于任意类型的数组,非class, struct,或者union类型的对象,编译器会调用全局函数——::operator new——来分配内存。类类型的对象可以定义他们自己的operator new静态成员函数。
在代码中,当用new运算符为类类型type的对象分配内存时,编译器会调用 type::operator new( sizeof( type ) )成员函数;当这个类没有自定义的operator new运算符时,编译器会调用::operator new(sizeof(type))全局函数。请注意,使用new运算符编译时,编译器会自动计算类的大小后传递给函数operator new 。这样,new 运算符能够给对象分配正确数量的内存。
new语法中的一个选项允许指定占位符placement中的参数。placement参数仅仅允许使用在类中自定义的operator new实现中;它允许传递给opeator new额外的信息。例如下面的表达式:

T *TObject = new ( 0x0040 ) T;

当类中具有自定义的new操作符时会被编译为

T *TObject = T::operator new( sizeof( T ), 0x0040 ); //请注意,T::operator new的第一个参数是编译器自动添加的

否则被编译为

T *TObject = ::operator new( sizeof( T ), 0x0040 ); //请注意,::operator new的第一个参数是编译器自动添加的

请注意,尽管上述例子中在placement字段中只有一个参数,但实际上可以传递给operator new的参数没有限制。
即便类类型中有自定义的operator new,全局的操作符依然可以通过以下方式使用

T *TObject =::new TObject;

作用域解析运算符(::)强制使用全局的new操作符。

三 operator new函数

上面说过了,new运算符会调用operator new函数,而根据作用域不同,operator new分为以下两种类型:

Operator Scope
::operator new Global
class-name::operator new Class

operator new函数的第一个参数类型必须是size_t(在STDDEF.H中定义),然后返回类型永远是void *.
当new操作符用在分配内置类型、无自定义operator new成员函数的类类型时、任意类型的数组时 ,编译器会调用全局的operator new函数。
当new 操作符用在分配一个有自定义operator new(静态)成员函数的类类型时 ,编译器会调用类自己的自定义operator new 函数。
类自定义operator new 函数是一个静态成员(因此,不可能是virtual了),在分配这个类对象的内存时,会隐藏全局的operator new 。考虑下面的例子。

// spec1_the_operator_new_function1.cpp
#include 
#include 

class Blanks
{
public:
    Blanks(){}
    void *operator new( size_t stAllocateBlock, char chInit );
};
void *Blanks::operator new( size_t stAllocateBlock, char chInit )
{
    void *pvTemp = malloc( stAllocateBlock );
    if( pvTemp != 0 )
        memset( pvTemp, chInit, stAllocateBlock );
    return pvTemp;
}
// For discrete objects of type Blanks, the global operator new function
// is hidden. Therefore, the following code allocates an object of type
// Blanks and initializes it to 0xa5
int main()
{
   Blanks *a5 = new(0xa5) Blanks;
   return a5 != 0;
}

new括号中的实参值0xa5传递给Blanks::operator new的chInit 参数。全局的operator new函数这时候被隐藏了,所以下面的代码会报错:

Blanks *SomeBlanks = new Blanks;	//error,参数错误,缺一个参数

在Visual C++ 5.0和更早的版本,用new 运算符分配非类类型和 所有数组(不管数组元素是否为类类型)均是调用全局的operator new函数。
从Visual C++ 5.0以后,编译器支持了成员数组new和delete函数

// spec1_the_operator_new_function2.cpp
class MyClass
{
public:
   void * operator new[] (size_t)
   {
      return 0;
   }
   void   operator delete[] (void*)
   {
   }
};

int main() 
{
   MyClass *pMyClass = new MyClass[5];
   delete [] pMyClass;
}

四 一个综合性的例子

在下面的例程中,你会理解如何给operator new传递参数、如何解除类对全局运算符new的屏蔽、new运算符如何初始化对象等等知识点。

#include 
#include 

class Blanks
{
public:
	Blanks() {}
	Blanks(int i, double d) {
		int m = i;
		double dd = d * 2;
	}

	//注意,这里重载了operator new函数,接受两个参数,在使用new运算符时,第一个参数编译器会自动传递进去
	void *operator new(size_t stAllocateBlock, char chInit);
};
void *Blanks::operator new(size_t stAllocateBlock, char chInit)
{
	void *pvTemp = malloc(stAllocateBlock);
	if (pvTemp != 0)
		memset(pvTemp, chInit, stAllocateBlock);
	return pvTemp;
}

int main()
{
	//1.理解如何给operator new传递参数
	//这一句实际上被转换为 Blanks *a1 =  Blanks::operator new(sizeof(Blanks),0xa5);
	Blanks *a1 = new(0xa5) Blanks;

	//2.理解何时会屏蔽全局的new	
	//Blanks *a2 = new Blanks;//error,因为类的自定义new屏蔽了全局new,而自定义的new有两个参数,

	//3.如何解除类对全局运算符new的屏蔽
	//即使类中有自定义的new,我们仍然可以通过作用域解析运算符使用全局new
	Blanks* a3 = ::new Blanks;

	//4.new运算符如何初始化对象
	//new的参数是通过new后面括号中参数进行传递的,而Blanks的初始化是通过Blanks其后的括号参数传递进去的
	Blanks* a4 = new(0xa5) Blanks(1, 7.5);
}

五 使用new可能导致的内存泄漏

如果使用无参数的new操作符,并且编译器设置了 /GX, /EHa, 或者/EHs选项,那么当new抛出异常时,编译器将会产生调用操作符delete的代码。(MSDN原文:If you use the operator new without any extra arguments, and compile with the /GX, /EHa, or /EHs option, the compiler will generate code to call operator delete if the constructor throws an exception. )
但是,如果使用new运算符的placement new形式(即除了分配大小之外还包含其他参数的形式),那么如果构造函数抛出异常,编译器将不支持delete运算符的placements形式。例如:

// expre_new_Operator2.cpp
// C2660 expected
class A {
public:
	A(int) { throw "Fail!"; }
};
void F(void) {
	try 
	{
		//剖出异常时,由于编译器自动产生了::operator delete(void*)代码,指向pal的堆内存会被释放
		// heap memory pointed to by pa1 will be deallocated
		// by calling ::operator delete(void*).
		A* pa1 = new A(10);
	}
	catch (...) {	}

	try 
	{
		//当A::A(int)抛出异常时,我们应当调用::operator delete(void*, char*, int)
		//去释放pa2指向的内存;但是因为::operator delete(void*, char*, int) 并没有实现,
		//析构无法发生,内存出现了泄漏
		// This will call ::operator new(size_t, char*, int).
		// When A::A(int) does a throw, we should call
		// ::operator delete(void*, char*, int) to deallocate
		// the memory pointed to by pa2.  Since
		// ::operator delete(void*, char*, int) has not been implemented,
		// memory will be leaked when the deallocation cannot occur.
		A* pa2 = new(__FILE__, __LINE__) A(20);
	}
	catch (...) {	}
}

int main() {
	A a;
}

你可能感兴趣的:(C/C++基础知识,c++)