¿Por qué no se llama al destructor en el operador delete?

16

Traté de llamar ::deletepara una clase en el operator deletemismo. Pero el destructor no se llama.

Definí una clase MyClassque operator deleteha sido sobrecargada. Lo global operator deletetambién está sobrecargado. El sobrecargado operator deletede MyClassllamará al global sobrecargado operator delete.

class MyClass
{
public:
    MyClass() { printf("Constructing MyClass...\n"); }
    virtual ~MyClass() { printf("Destroying MyClass...\n"); }

    void* operator new(size_t size)
    {
        printf("Newing MyClass...\n");
        void* p = ::new MyClass();
        printf("End of newing MyClass...\n");
        return p;
    }

    void operator delete(void* p)
    {
        printf("Deleting MyClass...\n");
        ::delete p;    // Why is the destructor not called here?
        printf("End of deleting MyClass...\n");
    }
};

void* operator new(size_t size)
{
    printf("Global newing...\n");
    return malloc(size);
}

void operator delete(void* p)
{
    printf("Global deleting...\n");
    free(p);
}

int main(int argc, char** argv)
{
    MyClass* myClass = new MyClass();
    delete myClass;

    return EXIT_SUCCESS;
}

El resultado es:

Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...

Real:

Solo hay una llamada al destructor antes de llamar al sobrecargado operator deletede MyClass.

Esperado:

Hay dos llamadas al destructor. Uno antes de llamar al sobrecargado operator deletede MyClass. Otro antes de llamar a lo global operator delete.

expinc
fuente
66
MyClass::operator new()debe asignar memoria en bruto, de (al menos) sizebytes. No debe intentar construir completamente una instancia de MyClass. El constructor de MyClassse ejecuta después MyClass::operator new(). Luego, la deleteexpresión in main()llama al destructor y libera la memoria (sin volver a llamar al destructor). La ::delete pexpresión no tiene información sobre el tipo de ppuntos de objeto , ya que pes a void *, por lo que no puede invocar al destructor.
Peter
Relacionado: stackoverflow.com/a/8918942/845092
Mooing Duck el
2
Las respuestas que ya le dieron son correctas, pero me pregunto: ¿por qué está tratando de anular nuevas y eliminar? El caso de uso típico es implementar una administración de memoria personalizada (GC, memoria que no proviene del malloc predeterminado (), etc.). Tal vez estás usando la herramienta incorrecta para lo que estás tratando de lograr.
noamtm
2
::delete p;provoca un comportamiento indefinido ya que el tipo de *pno es el mismo que el tipo del objeto que se está eliminando (ni una clase base con destructor virtual)
MM
@MM Los principales compiladores solo lo advierten a lo sumo, así que no me di cuenta de que el void*operando está explícitamente mal formado. [expr.delete] / 1 : " El operando será de puntero a tipo de objeto o de tipo de clase. [...] Esto implica que un objeto no puede eliminarse usando un puntero de tipo void porque void no es un tipo de objeto. * "@OP He modificado mi respuesta.
nuez

Respuestas:

17

Estás haciendo mal uso operator newy operator delete. Estos operadores son funciones de asignación y desasignación. No son responsables de construir o destruir objetos. Solo son responsables de proporcionar la memoria en la que se colocará el objeto.

Las versiones globales de estas funciones son ::operator newy ::operator delete. ::newy ::deleteson nuevas / eliminar-expresiones, como son new/ delete, que difieren de esas, en eso ::newy ::deleteomitirán las sobrecargas operator new/ específicas de clase operator delete.

Las nuevas expresiones / delete-build construyen / destruyen y asignan / desasignan (llamando al apropiado operator newo operator deleteantes de la construcción o después de la destrucción).

Dado que su sobrecarga solo es responsable de la parte de asignación / desasignación, debe llamar ::operator newy en ::operator deletelugar de ::newy ::delete.

El deletein delete myClass;es responsable de llamar al destructor.

::delete p;no llama al destructor porque ptiene tipo void*y, por lo tanto, la expresión no puede saber a qué destructor llamar. Probablemente llamará a su reemplazo ::operator deletepara desasignar la memoria, aunque el uso de un void*operando as para una expresión de eliminación está mal formado (vea la edición a continuación).

::new MyClass();llama a su reemplazado ::operator newpara asignar memoria y construye un objeto en él. El puntero a este objeto se devuelve en cuanto void*a la nueva expresión en MyClass* myClass = new MyClass();, que luego construirá otro objeto en esta memoria, terminando la vida útil del objeto anterior sin llamar a su destructor.


Editar:

Gracias al comentario de @ MM sobre la pregunta, me di cuenta de que un void*como operando ::deleteestá mal formado. ( [expr.delete] / 1 ) Sin embargo, los principales compiladores parecen haber decidido advertir sobre esto, no por error. Antes de que estuviera mal formado, usando ::deleteun void*comportamiento ya indefinido, vea esta pregunta .

Por lo tanto, su programa está mal formado y no tiene ninguna garantía de que el código realmente haga lo que describí anteriormente si aún logra compilar.


Como señaló @SanderDeDycker debajo de su respuesta, también tiene un comportamiento indefinido porque al construir otro objeto en la memoria que ya contiene un MyClassobjeto sin llamar primero al destructor de ese objeto, está violando [basic.life] / 5 que prohíbe hacerlo si el El programa depende de los efectos secundarios del destructor. En este caso, la printfdeclaración en el destructor tiene tal efecto secundario.

nuez
fuente
El mal uso está destinado a verificar cómo funciona este operador. Sin embargo, gracias por tu respuesta. Parece la única respuesta que resuelve mi problema.
expinc
13

Sus sobrecargas específicas de clase se realizan incorrectamente. Esto se puede ver en su salida: ¡se llama al constructor dos veces!

En la clase específica operator new, llame al operador global directamente:

return ::operator new(size);

Del mismo modo, en la clase específica operator delete, hacer:

::operator delete(p);

Consulte la operator newpágina de referencia para más detalles.

Sander De Dycker
fuente
Sé que el constructor se llama dos veces llamando a :: new en operator new y lo digo en serio. Mi pregunta es ¿por qué no se llama al destructor cuando se llama :: eliminar en el operador eliminar?
expinc
1
@expinc: Llamar intencionalmente al constructor por segunda vez sin llamar primero al destructor es una muy mala idea. Para los destructores no triviales (como el suyo), incluso se aventura en territorio de comportamiento indefinido (si depende de los efectos secundarios del destructor, lo que hace) - ref. [vida.básica] §5 . No hagas esto.
Sander De Dycker
1

Ver referencia de CPP :

operator delete, operator delete[]

Desasigna el almacenamiento previamente asignado por una coincidencia operator new. Estas funciones de desasignación se llaman mediante expresiones de eliminación y mediante nuevas expresiones para desasignar la memoria después de destruir (o no construir) objetos con una duración de almacenamiento dinámico. También pueden llamarse utilizando la sintaxis de llamada a función regular.

Delete (y new) solo son responsables de la parte de 'gestión de memoria'.

Por lo tanto, está claro y se espera que el destructor solo se llame una vez, para limpiar la instancia del objeto. Se llamaría dos veces, cada destructor tendría que verificar si ya se había llamado.

Mario la cuchara
fuente
1
El operador de eliminación aún debe llamar implícitamente al destructor, como puede ver en sus propios registros que muestran el destructor después de eliminar la instancia. El problema aquí es que su anulación de eliminación de clase está llamando a :: eliminar, lo que conduce a su anulación de eliminación global. Esa anulación de eliminación global simplemente libera memoria para que no vuelva a invocar el destructor.
Pickle Rick el
La referencia establece claramente que eliminar se llama DESPUÉS de la deconstrucción de objetos
Mario The Spoon
Sí, la eliminación global se llama después de la eliminación de la clase. Hay dos anulaciones aquí.
Pickle Rick
2
@PickleRick: si bien es cierto que una expresión de eliminación debe llamar a un destructor (suponiendo que se haya suministrado un puntero a un tipo con un destructor) o un conjunto de destructores (forma de matriz), una operator delete()función no es lo mismo que una expresión de eliminación. Se llama al destructor antes de llamar a la operator delete()función.
Peter
1
Sería útil si agregara un encabezado a la qoute. Actualmente no está claro a qué se refiere. "Deslocaliza el almacenamiento ...": ¿quién desasigna el almacenamiento?
idclev 463035818