为了避免内存泄漏,每个动态内存分配必须有与一个相反的解除分配(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