¿Cómo liberar correctamente la memoria asignada por ubicación nueva?

He estado leyendo que si usas la ubicación nueva, entonces debes llamar al destructor manualmente.

Considera el siguiente código:

// Allocate memory ourself char* pMemory = new char[ sizeof(MyClass)]; // Construct the object ourself MyClass* pMyClass = new( pMemory ) MyClass(); // The destruction of object is our duty. pMyClass->~MyClass(); 

Por lo que sé, el operador delete normalmente llama al destructor y luego desasigna la memoria, ¿verdad? Entonces, ¿por qué no utilizamos delete lugar?

 delete pMyClass; //what's wrong with that? 

en el primer caso, nos vemos obligados a establecer pMyClass en nullptr después de llamar a destructor de esta manera:

 pMyClass->~MyClass(); pMyClass = nullptr; // is that correct? 

PERO el destructor NO desasoció la memoria, ¿verdad? Entonces, ¿sería una fuga de memoria?

Estoy confundido, ¿puedes explicar eso?

    Usar el new operador hace dos cosas, llama al operator new función operator new que asigna memoria, y luego usa la colocación nueva, para crear el objeto en esa memoria. El operador de delete llama al destructor del objeto y luego llama al operator delete . Sí, los nombres son confusos.

     //normal version calls these two functions MyClass* pMemory = new MyClass; void* pMemory = operator new(sizeof(MyClass)); MyClass* pMyClass = new( pMemory ) MyClass(); //normal version calls these two functions delete pMemory; pMyClass->~MyClass(); operator delete(pMemory); 

    Como en su caso, utilizó la colocación nueva manualmente, también necesita llamar al destructor manualmente. Como asignó la memoria manualmente, necesita liberarla manualmente. Recuerde, si asigna con new , debe haber una delete coressponding. Siempre.

    Sin embargo, la ubicación nueva está diseñada para funcionar también con búferes internos (y otros escenarios), donde los búferes no fueron asignados con el operator new , por lo que no debe invocar el operator delete en ellos.

     #include  struct buffer_struct { std::aligned_storage::type buffer; }; int main() { buffer_struct a; MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a //stuff pMyClass->~MyClass(); //can't use delete, because there's no `new`. return 0; } 

    El objective de la clase buffer_struct es crear y destruir el almacenamiento de cualquier forma, mientras que main se encarga de la construcción / destrucción de MyClass , observe cómo los dos están (casi *) completamente separados el uno del otro.

    * tenemos que estar seguros de que el almacenamiento debe ser lo suficientemente grande

    Una razón por la que esto está mal:

     delete pMyClass; 

    es que debes eliminar pMemory con delete[] ya que es una matriz:

     delete[] pMemory; 

    No puedes hacer las dos cosas de arriba.

    De manera similar, puede preguntar por qué no puede usar malloc() para asignar memoria, colocación nueva para construir un objeto, y luego delete para eliminar y liberar la memoria. La razón es que debe hacer coincidir malloc() y free() , no malloc() y delete .

    En el mundo real, casi nunca se utilizan las llamadas de destrucción nuevas y explícitas de ubicación. Pueden ser utilizados internamente por la implementación de la Biblioteca estándar (o para otros progtwigs de nivel de sistema como se indica en los comentarios), pero los progtwigdores normales no los usan. Nunca utilicé tales trucos para el código de producción en muchos años de hacer C ++.

    Debe distinguir entre el operador de delete y el operator delete . En particular, si está utilizando la ubicación nueva, invoque explícitamente el destructor y luego llame al operator delete (y no al operador de delete ) para liberar la memoria, es decir,

     X *x = static_cast(::operator new(sizeof(X))); new(x) X; x->~X(); ::operator delete(x); 

    Tenga en cuenta que esto utiliza el operator delete , que es de nivel más bajo que el operador de delete y no se preocupa por los destructores (es esencialmente un poco como free ). Compare esto con el operador de delete , que internamente hace el equivalente de invocar el destructor y operator delete llama.

    Vale la pena señalar que no es necesario utilizar ::operator new y ::operator delete para asignar y desasignar el búfer; en lo que se refiere a la ubicación nueva, no importa cómo se genere / destruya el búfer. El punto principal es separar las preocupaciones de la asignación de memoria y la duración del objeto.

    Por cierto, una posible aplicación de esto sería en algo así como un juego, donde es posible que desee asignar un gran bloque de memoria por adelantado con el fin de administrar cuidadosamente el uso de la memoria. Luego construirías objetos en la memoria que ya has adquirido.

    Otro posible uso sería un asignador de objetos pequeños, de tamaño fijo optimizado.

    Probablemente sea más fácil de entender si imagina construir varios objetos MyClass dentro de un bloque de memoria.

    En ese caso, sería algo así como:

    1. Asigne un bloque de memoria gigante usando la nueva char [10 * sizeof (MyClass)] o malloc (10 * sizeof (MyClass))
    2. Use la ubicación nueva para construir diez objetos MyClass dentro de esa memoria.
    3. Hacer algo.
    4. Llamar al destructor de cada uno de tus objetos
    5. Desasigne el bloque grande de memoria usando delete [] o free ().

    Este es el tipo de cosas que podría hacer si está escribiendo un comstackdor, un sistema operativo, etc.

    En este caso, espero que esté claro por qué necesita pasos separados de “destrucción” y “eliminación”, porque no hay ninguna razón por la que llame a eliminar. Sin embargo, debe desasignar la memoria, sin embargo, normalmente lo haría (liberar, eliminar, no hacer nada por una matriz estática gigante, salir normalmente si la matriz es parte de otro objeto, etc., etc.), y si no lo hace ‘ Se filtró.

    También tenga en cuenta que, como dijo Greg, en este caso, no puede usar delete porque asignó la matriz con new [], por lo que necesitaría usar delete [].

    También tenga en cuenta que debe suponer que no ha anulado la eliminación de MyClass, de lo contrario, hará algo totalmente diferente, que es casi seguro incompatible con “nuevo”.

    Así que creo que es poco probable que desee llamar a “eliminar” como usted describe, pero ¿podría funcionar alguna vez? Creo que esta es básicamente la misma pregunta que “Tengo dos tipos no relacionados que no tienen destructores. ¿Puedo nuevo un puntero a un tipo, luego eliminar esa memoria a través de un puntero a otro tipo?” En otras palabras, “mi comstackdor tiene una gran lista de todas las cosas asignadas, o puede hacer cosas diferentes para diferentes tipos”.

    Me temo que no estoy seguro. Leyendo la especificación dice:

    5.3.5 … Si el tipo estático del operando [del operador de eliminación] es diferente de su tipo dynamic, el tipo estático será una clase base del tipo dynamic del operando y el tipo estático tendrá un destructor virtual o el el comportamiento no está definido

    Creo que eso significa que “si usas dos tipos no relacionados, no funciona (está bien eliminar un objeto de clase polimórficamente a través de un destructor virtual)”.

    Entonces no, no hagas eso. Sospecho que a menudo puede funcionar en la práctica, si el comstackdor solo mira la dirección y no el tipo (y ningún tipo es una clase de herencia múltiple, lo que destruiría la dirección), pero no lo intente.