假设我有以下代码(https://godbolt.org/z/MW4ETf7a8):
犃
#include <iostream>
struct X{
void* operator new(std::size_t size)
{
std::cout << "new X\n";
return malloc(size);
}
void operator delete(void* ptr)
{
std::cout << "delete X\n";
return free(ptr);
}
virtual ~X() = default;
};
struct ArenaAllocatedX : public X {
void* operator new(std::size_t size)
{
std::cout << "new ArenaAllocatedX\n";
return malloc(size);
}
void operator delete(void* ptr)
{
std::cout << "delete ArenaAllocatedX\n";
return free(ptr);
}
};
主程序
int main() {
X* x1 = new X();
delete x1;
X* x2 = new ArenaAllocatedX ();
delete x2;
}
使用 GCC 10.3.1,
x1
调用的new
和delete
操作符class X
,同时x2
调用class ArenaAllocatedX
。
我理解它将如何选择new
运算符,因为它的右边有实际的类名,但我不明白delete
在被指向时如何为子类选择运算符X*
。
new
/运算符是否被delete
视为虚拟表内的函数(我使用转储了 VTablegcc -f-dump-lang-class
但没有看到任何删除运算符)?
VTable转储:
Vtable for X
X::_ZTV1X: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI1X)
16 (int (*)(...))X::~X
24 (int (*)(...))X::~X
Class X
size=8 align=8
base size=8 base align=8
X (0x0x7f38cd807780) 0 nearly-empty
vptr=((& X::_ZTV1X) + 16)
Vtable for ArenaAllocatedX
ArenaAllocatedX::_ZTV15ArenaAllocatedX: 4 entries
0 (int (*)(...))0
8 (int (*)(...))(& _ZTI15ArenaAllocatedX)
16 (int (*)(...))ArenaAllocatedX::~ArenaAllocatedX
24 (int (*)(...))ArenaAllocatedX::~ArenaAllocatedX
Class ArenaAllocatedX
size=8 align=8
base size=8 base align=8
ArenaAllocatedX (0x0x7f38cd848680) 0 nearly-empty
vptr=((& ArenaAllocatedX::_ZTV15ArenaAllocatedX) + 16)
X (0x0x7f38cd807cc0) 0 nearly-empty
primary-for ArenaAllocatedX (0x0x7f38cd848680)
C++ 如何选择delete
在所提供的代码中使用哪个运算符?
正如您在 vtable 中看到的,每个类都有两个析构函数。
如果您有一个带有虚析构函数的类,那么编译器将发出该析构函数的两个实例。
一种是普通析构函数,它执行与非虚拟析构函数相同的操作,并且在您明确调用析构函数时会使用该函数。
第二个是所谓的删除析构函数。它也执行通常的析构函数操作(或调用第一个实现),但最后还会调用
operator delete
析构函数所属类的。析构函数本身是虚拟的,并且可以通过虚拟查找找到派生程度最高的对象的两个析构函数实现。对于表达式,
delete
编译器将通过虚拟调度调用删除析构函数。由于调用operator delete
嵌入到该析构函数中,因此编译器不需要operator delete
在表达式的位置添加任何额外的调用delete
。然后,“通常的”析构函数也称为基对象析构函数,因为它将用于基类子对象(如果有)的情况,而这些子对象本身没有相关的分配。
由于这种特殊的实现,
operator delete
虚拟析构函数的查找规则也不同,模板实例化规则也不同。operator delete
将在虚拟析构函数的定义点而不是表达式出现的位置进行查找delete
。这也可能导致令人困惑的结果。