Vamos supor que eu tenha o seguinte código ( https://godbolt.org/z/MW4ETf7a8 ):
Xh
#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);
}
};
principal.cpp
int main() {
X* x1 = new X();
delete x1;
X* x2 = new ArenaAllocatedX ();
delete x2;
}
Usando o GCC 10.3.1,
x1
chama os operadores new
e de , enquanto chama de .delete
class X
x2
class ArenaAllocatedX
Eu entendo como ele pegará o new
operador, já que ele tem o nome real da classe à sua direita, mas não entendo como o delete
operador é escolhido para a subclasse enquanto é apontado por X*
.
Os operadores new
/ são delete
considerados funções dentro da tabela virtual (descartei o VTable usando gcc -f-dump-lang-class
e ainda não vejo nenhum operador de exclusão)?
Os despejos do 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)
Como o C++ escolhe qual delete
operador usar no código fornecido?
Como você pode ver na sua tabela, existem dois destruidores para cada classe.
Se você tiver uma classe com um destruidor virtual, o compilador emitirá duas instâncias do destruidor.
Um é o destruidor normal que executa as mesmas operações que um destruidor não virtual faria e que seria usado se você chamasse explicitamente o destruidor.
O segundo é o chamado destruidor de exclusão . Ele também executa as ações usuais do destruidor (ou chama a primeira implementação), mas adicionalmente, no final, chamará o
operator delete
da classe à qual o destruidor pertence.O destruidor em si é virtual e ambas as implementações do destruidor podem ser encontradas para o objeto mais derivado por pesquisa virtual. Para uma
delete
expressão, o compilador chamará o destruidor de exclusão por despacho virtual. Como aoperator delete
chamada está incorporada nesse destruidor, o compilador não precisará adicionar nenhumaoperator delete
chamada adicional no local dadelete
expressão.O destruidor "usual" também é chamado de destruidor de objeto base , porque será usado no caso de subobjetos de classe base (se houver), que não possuem uma alocação associada.
Devido a essa implementação especial,
operator delete
as regras de pesquisa também são diferentes com destruidores virtuais e as regras de instanciação de modelos são diferentes.operator delete
será consultado no ponto de definição do destruidor virtual, não no ponto onde adelete
expressão aparece. Isso também pode levar a resultados confusos.