C++ delete的三种面貌

为了避免内存泄漏,每个动态内存分配必须有与一个相反的解除分配(Deallocation)操作对应,所以C++中有new操作,那么就存在相反的delete操作,new与delete的关系,就像C语言中malloc()与free()的关系,分别负责内存的申请与释放,只不过C++中的new与delete赋予了其它的功能。当我们使用delete运算符来释放一个由new创建的对象时,我们应该清楚delete完成的工作有:
(1)调用对象析构函数;
(2)调用operater delete()函数释放内存空间。

所以在使用delete释放对象时,我们要认清delete的三种面貌,分别是:delete operator、operator delete()与placement delete()。delete的三种面貌与new的三种面貌一一对应,分别是new operator、new delete()与placement new(),关于new的三种面貌,参见博文C++ new的三种面貌

1.delete operator

delete operator是C++保留关键词,无法改变其含义,但我们可以改变delete时调用的函数operator delete()。operator delete()与delete运算符的关系与operator new()与new操作符是一样的,operator delete()用于释放operator new()申请的内存空间,对象的析构函数用来清理placement new()初始化的内存数据。所以如下示例代码:

string* sp=new string(“hello world”);
delete sp;

调用delete sp等价于:

ps->~string(); 				//用于清理内存数据,对应placement new()
operator delete(ps);		//释放内存空间,对应于operator new()

2.operator delete()

operator new()用于申请Heap空间,那么operator delete()用于释放Heap空间,申明见头文件<new>,其函数定义如下:

void operator delete(void *memoryToBeDeallocated) noexcept
{
	std::free(ptr);
}

下面看一下对operator delete()重载的实例。

#include <iostream>
using namespace std;

class A
{
public:
	void operator delete(void* ptr)
	{
		cout << "this is user defined operator delete()" << endl;
		std::free(ptr);
	}

	~A()
	{
		cout<<"this is A's destructor"<<endl;
	}
};

int main()
{
	A* pA1 = new A();
	delete pA1;			//调用用户自定义operator delete();

	A* pA2 = new A();
	::delete pA2;		//调用全局operator delete()

	int* pInt = new int;
	delete pInt;		//调用全局operator delete()
}

程序输出结果:

this is A's destructor
this is user defined operator delete()
this is A's destructor

阅读以上程序,注意以下几点:
(1)对于不是类类型(class、struct 或 union)的对象,将调用全局 delete 运算符;
(2)于类类型的对象,如果重载operator delete(),则在释放对象时默认调用重载版本,可以使用作用域运算符(::)置于delete之前,显示调用全局operator delete();
(3)delete运算符在释放对象之前会调用对象析构函数。考察如下代码在VS2017中对应的汇编代码:

delete pA1;			//调用用户自定义operator delete();

//对应的汇编代码
//segment 1
000000013F6B101C  lea         rdx,[string "this is A's destructor" (013F6B3350h)]  
000000013F6B1023  mov         rcx,qword ptr [__imp_std::cout (013F6B30C8h)]  
000000013F6B102A  call        std::operator<<<std::char_traits<char> > (013F6B1080h)  
000000013F6B102F  mov         rcx,rax  
000000013F6B1032  lea         rdx,[std::endl<char,std::char_traits<char> > (013F6B1250h)]  
000000013F6B1039  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F6B3080h)]  

//segment 2
000000013F6B103F  nop  
000000013F6B1040  lea         rdx,[string "this is user defined operator de"... (013F6B3328h)]  
000000013F6B1047  mov         rcx,qword ptr [__imp_std::cout (013F6B30C8h)]  
000000013F6B104E  call        std::operator<<<std::char_traits<char> > (013F6B1080h)  
000000013F6B1053  mov         rcx,rax  
000000013F6B1056  lea         rdx,[std::endl<char,std::char_traits<char> > (013F6B1250h)]  
000000013F6B105D  call        qword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (013F6B3080h)]  
000000013F6B1063  mov         rcx,rbx  
000000013F6B1066  call        qword ptr [__imp_free (013F6B3120h)]  
000000013F6B106C  nop

其中segment 1为调用类A的析构函数输出“this is A’s destructor”,segment 2表示调用operator delete(),用于输出“this is user defined operator delete()”并调用free()函数。可见delete运算符在释放对象占用的内存空间之前会调用对象析构函数,也就验证了delete释放对象时完成两项工作:
(1)调用对象析构函数;
(2)调用operater delete()函数释放内存空间。

3.placement delete()

placement new()是operator new()附带额外参数的重载版本,对应的,placement delete()即operator delete()的重载版本,默认实现版本如下:

void operator delete  (void*, void*) noexcept { }

默认placement delete()的额外参数是void*,为空实现,什么也不做。当然,我们可以定义其它附带类型的重载版本,这里使用默认版本placement new()与重载placement delete()来演示定位构造对象和析构对象。

#include <iostream>
using namespace std;

class A
{
	int num;
public:
	A()
	{
		cout << "A's constructor" << endl;
	}

	~A()
	{
		cout << "A's destructor" << endl;
	}

	void show()
	{
		cout << "num:" << num << endl;
	}

	//自定义placement delete()
	static void operator delete(void* a, void* b)
	{
		A*  pA= (A*)a;
		pA->~A();
	}
};

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;							//调用placement new(),间接调用类A的构造函数,构造类A对象
	cout << p << endl;
	p->show();
	A::operator delete(p,(void*)NULL) ;			//调用placement delete(),间接调用类A的析构函数,释放类A对象
}

程序运行结果:

000000000014F8E0
A's constructor
000000000014F8E0
num:65
A's destructor

阅读以上程序,需要注意以下几点:
(1)C++标准中默认版本的placement delete()为空实现,不调用类型对象析构函数;
(2)C++中placement delete()的调用没有像placement new()的调用有单独的语法格式(placement new expression),需要以函数调用的书写格式来调用;
(3)上面对placement delete()的重载只是实现了调用类对象析构函数的功能,实际通过类对象直接调用析构函数即可,那placement delete()存在的意义是什么呢?一个重要的原因是, C++需要placement delete()和placement new()成双成对。假设有一种情况当你调用placement new expression构建对象,结果在构造函数中抛出异常,这个时候怎么办,C++只能调用相应的placement delete()释放由placement new()获取的内存资源,否则就会有内存泄露。通过下面的例子可以感受一下:

#include <iostream>
using namespace std;

struct A {};	//临时中间对象
struct E {};	//异常对象

class T 
{
public:
	T()
	{ 
		std::cout << "T's constructor" << endl;
		throw E(); 
	}
};

void* operator new (std::size_t, const A& a)
{
	std::cout << "Placement new called." << std::endl;
	return (void*)&a;
}

void operator delete (void *, const A &)
{
	std::cout << "Placement delete called." << std::endl;
}

int main()
{
	A a;
	try 
	{
		T* p = new(a) T;
	}
	catch (E exp)
	{ 
		std::cout << "Exception caught." << std::endl;
	}
	return 0;
}

程序输出结果:

Placement new called.
T's constructor
Placement delete called.
Exception caught.

当在class T构造函数中抛出异常时,对应版本的placement delete()将被调用,所谓的对应版本,即placement delete()附加参数类型相同。


参考文献

[1]理解C++ placement语法
[2]Scott Meyers. Effective C++ 3rd edition. 2005.条款52

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值