c++ new操作符(new operator)、operator new、placement new 、operator new[] 及相对应的delete 操作符、operator delete

一.new  

new operator就是new操作符,不能被重载,假如A是一个类,那么A * a=new A;实际上执行如下3个过程。  
(1)调用operator new分配内存,operator new (sizeof(A))  
(2)调用构造函数生成类对象,A::A()  
(3)返回相应指针  
事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),否则调用全局::operator new(size_t ),后者由C++默认提供。

C++中虽然不允许定义长度为0的数组,但明确指出 动态分配new 长度为0的数组 是合法的,他返回的是一个合法的非0指针,这个指针与一般new返回的指针有所不同,他不能进行读(解引用)操作(如果进行解引用操作,那么结果是undefined 的)。更不能进行写操作,不然调用delete函数时,会报错崩溃。

但 如果是 如 int a[0] 这样就会直接报错。

Widget* pw = new Widget; //使用正常签名式的new创建对象
delete pw;
  • 对于使用正常签名式的new来创建对象时,其可能在两个地方会抛出异常:
    • ①当在调用new()时,new()函数中可能会抛出异常
    • ②如果new()函数没有抛出异常,但是后面对象的构造函数可能会抛出异常。系统会自动调用delete()来释放new()所做的一切,使其恢复原状(释放内存等)。
    • 分析:构造函数抛出异常,指针尚未被赋值,客户端无法取得该指针归还内存,因此此处的内存归还操作就交给了C++运行期系统身上。注意:该delete操作不会调用对象的析构函数(前提是要处理掉异常),只释放内存。

二.operator new  

operator new是函数,分为三种形式(前2种不调用构造函数,这点区别于new operator):  
void* operator new (std::size_t size) throw (std::bad_alloc);  
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();  
void* operator new (std::size_t size, void* ptr) throw();  
第一种分配size个字节的存储空间,并将对象类型进行内存对齐。如果成功,返回一个非空的指针指向首地址。失败抛出bad_alloc异常。  
第二种在分配失败时不抛出异常,它返回一个NULL指针。  
第三种是placement new版本,它本质上是对operator new的重载,定义于#include <new>中。它不分配内存,调用合适的构造函数在ptr所指的地方构造一个对象,之后返回实参指针ptr。  
第一、第二个版本可以被用户重载,定义自己的版本,第三种placement new不可重载。  
A* a = new A; //调用第一种  
A* a = new(std::nothrow) A; //调用第二种  
new (p)A(); //调用第三种  
new (p)A()调用placement new之后,还会在p上调用A::A(),这里的p可以是堆中动态分配的内存,也可以是栈中缓冲。  

注意:

operator new 函数会被子类继承的,所以参数size 的大小要注意。

三.placement new

一般来说,使用new申请空间时,是从系统的“堆”(heap)中分配空间。申请所得的空间的位置是根据当时的内存的实际使用情况决定的。但是,在某些特殊情况下,可能需要在已分配的特定内存创建对象,这就是所谓的“定位放置new”(placement new)操作。  
定位放置new操 作的语法形式不同于普通的new操作。例如,一般都用如下语句A* p=new A;申请空间,而定位放置new操作则使用如下语句A* p=new (ptr)A;申请空间,其中ptr就是程序员指定的内存首地址.

#include <iostream>
using namespace std;
 
class A
{
public:
	A(int n)
	{
		cout << "A's constructor" << endl;
        num = n;
	}
 
 
	~A()
	{
		cout << "A's destructor" << endl;
	}
	
	virtual void show()
	{
		cout << "num:" << num << endl;
	}
	
private:
	int num;
};
 
int main()
{
	char mem[100];
	mem[0] = 'A';
	mem[1] = '\0';
	mem[2] = '\0';
	mem[3] = '\0';
	cout << (void*)mem << endl;
	A* p = new (mem)A(2);
	cout << p << endl;
	p->show();
	p->~A();
	getchar();
}

注意以下几点。   
(1)用定位放置new操作,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。如本例就是在栈上生成一个对象。  
(2)使用语句A* p=new (mem) A;定位生成对象时,指针p和数组名mem指向同一片存储区。所以,与其说定位放置new操作是申请空间,还不如说是利用已经请好的空间,真正的申请空间的工作是在此之前完成的。  
(3)使用语句A *p=new (mem) A;定位生成对象时,会自动调用类A的构造函数,但是由于对象的空间不会自动释放(对象实际上是借用别人的空间),所以必须显示的调用类的析构函数,如本例

在堆上:

class EquipmentPiece {
public:
	EquipmentPiece(int IDNumber) {}
	virtual ~EquipmentPiece() {}
	int a = 1;
	float b = 2.0;
};

	void* rawMemorysingle = operator new(sizeof(EquipmentPiece));
	EquipmentPiece* bestPiecesrawMemorysingle = static_cast<EquipmentPiece*> 
    (rawMemorysingle);
	new(bestPiecesrawMemorysingle) EquipmentPiece(1);
	bestPiecesrawMemorysingle->~EquipmentPiece();
	operator delete(rawMemorysingle);

更近一步:

//调用placement new创建对象
Widget* pw = new (std::cerr) Widget;

//调用正常签名式的delete删除对象
delete pw;

1.如果placement new未抛出异常,那么不会有任何事情
2.如果placement new未抛出异常,而当Widget的构造函数抛出异常时。需要自定义好placement delete 函数,这样系统就会自动调用placement delete 函数释放内存。

因为:此时pw指针,pw指针尚未被赋值,客户端无法取得该指针归还内存(此时出现的情况于“一”中的演示案例一样)。
但是此时我们使用的是placement new创建的对象,因此C++系统回去查找是否有placement delete来进行恢复原状,但是Widget没有定义,因此运行期系统不知道如何取消并恢复原先对placement new的调用

注意:如果没有抛出异常,delete pw;调用的是正常形式的operator delete,而不是placement delete.

四、delete 操作符

A * a=new A;

delete a;

delete也分两部分的操作。
(1)首先调用A ::~A ()将对象析构

(2)调用::operator delete释放内存

如果在对象的析构函数出现异常,并且不通过try catch 捕捉。那么系统不会自动调用::operator delete释放内存。结果是析构函数异常后面的代码都不执行,对象本身内存也不释放,出现内存泄漏。

处理办法:析构函数不能出现异常,如果出现异常,必须使用try catch 捕捉吞掉,不让异常套路析构函数。

延伸:

a.如果只想处理未被初始化的内存,应该绕过new和delete 操作符,而是调用operator new 获取内存和operator delete释放内存给系统。 

b.如果你用placement new在内存中建立对象,你应该避免在该内存中用delete操作符。因为delete操作符调用operator delete来释放内存,但是包含对象的内存最初不是被operator new分配的,placement new只是返回转到给它的指针。而是应该显性调用对象的析构函数来解除析构函数的影响。

class EquipmentPiece {
public:
	EquipmentPiece(int IDNumber) {}
	virtual ~EquipmentPiece() {}
	int a = 1;
	float b = 2.0;
};


	void* rawMemorysingle = operator new(sizeof(EquipmentPiece));
	EquipmentPiece* bestPiecesrawMemorysingle = static_cast<EquipmentPiece*> 
    (rawMemorysingle);
	new(bestPiecesrawMemorysingle) EquipmentPiece(1);
	bestPiecesrawMemorysingle->~EquipmentPiece();
	operator delete(rawMemorysingle);

五、new A[] (即new 数组)

 1.先调用operator new[ ]分配一个 连续的内存块。

2.对每个数组元素(即对象)调用缺省(或无参数)的构造函数。

如:string *ps = new string[10];

同理 delete [ ]A;   如:  delete  [ ]ps;

1.先为每个数组元素调用析构函数

2.调用 operator delete [] 释放内存。

class EquipmentPiece {
public:
	EquipmentPiece(int IDNumber) {}
	virtual ~EquipmentPiece() {}
	int a = 1;
	float b = 2.0;
};
	void* rawMemory = operator new[](10 * sizeof(EquipmentPiece));
	EquipmentPiece* bestPieces6 = static_cast<EquipmentPiece*>(rawMemory);

	for (int i = 0; i < 10; ++i)
		new(&bestPieces6[i]) EquipmentPiece(i);

	for (int i = 9; i >= 0; --i)
		bestPieces6[i].~EquipmentPiece(); // 如果使用普通的数组删除方法,程序的运行将是不可预测的

	operator delete[](rawMemory);

扩展:

1.在C++中有一条规则是每一个重载的operator必须带有一个用户定义类型(user-defined type)的参数

2.new和mallco的区别?
1、new 是c++中的运算符.malloc是c/c++ 中的一个标准库函数.
2、new 不止是分配内存,而且会调用类的构造函数,同理delete会调用类的析构函数.而malloc则只分配内存,不会进行初始化类成员的工作.new可以认为是malloc加构造函数的执行。同样free也不会调用析构函数.
3.new出来的指针是直接带类型信息的。
而malloc返回的都是void指针。所以new是类型安全的,malloc不是

3.关于new和malloc以及delete和free能否够混用
1.malloc申请的空间能够用delete和free释放
2.new申请的空间不能用free释放(因为没有调用析构函数,也不能实现delete[] ),能够用delete释放.

4.不能重载的运算符
1 . (点运算符)通常用于去对象的成员,但是->(箭头运算符),是可以重载的 
2 ::(域运算符)即类名+域运算符,取成员,不可以重载 
3 .* (点星运算符,)不可以重载,成员指针运算符".*,即成员是指针类型 
4 ?: (条件运算符)不可以重载 
5 sizeof 不可以重载

  • 4
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值