destructores delete constructores c++ c++11 destructor

constructores - delete c++



Usos de destructor=eliminar; (5)

Considera la siguiente clase:

struct S { ~S() = delete; };

En breve y con el propósito de la pregunta: No puedo crear instancias de S como S s{}; porque no podría destruirlos.
Como mencioné en los comentarios, aún puedo crear una instancia haciendo S *s = new S; , pero no puedo borrarlo también
Por lo tanto, el único uso que puedo ver para un destructor eliminado es algo como esto:

struct S { ~S() = delete; static void f() { } }; int main() { S::f(); }

Es decir, defina una clase que exponga solo un grupo de funciones estáticas y prohíba cualquier intento de crear una instancia de esa clase.

¿Cuáles son los otros usos (si hay alguno) de un destructor eliminado?


Crear una instancia de un objeto con new y nunca eliminarlo es la forma más segura de implementar un Singleton de C ++, ya que evita todos y cada uno de los problemas de orden de destrucción. Un ejemplo típico de este problema sería un Singleton "Logging" al que se está accediendo en el destructor de otra clase Singleton. Alexandrescu una vez dedicó una sección entera en su libro clásico "Modern C ++ Design" sobre formas de lidiar con los problemas del orden de destrucción en las implementaciones de Singleton.

Es bueno tener un destructor eliminado para que incluso la clase Singleton no pueda eliminar accidentalmente la instancia. También evita el uso loco como delete &SingletonClass::Instance() (si Instance() devuelve una referencia, como debería, no hay ninguna razón para que devuelva un puntero).

Al final del día, nada de esto es realmente notable, sin embargo. Y, por supuesto, no debes usar Singletons en primer lugar de todos modos.


Hay dos casos de uso plausibles. En primer lugar (como señalan algunos comentarios) podría ser aceptable asignar dinámicamente objetos, no delete y permitir que el sistema operativo se limpie al final del programa.

Alternativamente (y aún más extraño) podría asignar un búfer y crear un objeto en él y luego eliminar el búfer para recuperar el lugar, pero nunca solicitar un intento de llamar al destructor.

#include <iostream> struct S { const char* mx; const char* getx(){return mx;} S(const char* px) : mx(px) {} ~S() = delete; }; int main() { char *buffer=new char[sizeof(S)]; S *s=new(buffer) S("not deleting this...");//Constructs an object of type S in the buffer. //Code that uses s... std::cout<<s->getx()<<std::endl; delete[] buffer;//release memory without requiring destructor call... return 0; }

Ninguno de estos parece una buena idea, excepto en circunstancias especiales. Si el destructor creado automáticamente no hiciera nada (porque el destructor de todos los miembros es trivial), entonces el compilador creará un destructor sin efecto.

Si el destructor creado automáticamente haría algo no trivial, es muy probable que comprometa la validez de su programa al no ejecutar su semántica.

Permitir que un programa salga de main() y permitir que el entorno "limpie" es una técnica válida, pero es mejor evitarla a menos que las restricciones lo hagan estrictamente necesario. ¡En el mejor de los casos es una excelente manera de enmascarar fugas de memoria genuinas!

Sospecho que la característica está presente para completar con la capacidad de delete otros miembros generados automáticamente.

Me encantaría ver un uso práctico real de esta capacidad.

Existe la noción de una clase estática (sin constructores) y, lógicamente, no requiere destructor. Pero tales clases se implementan más apropiadamente ya que un namespace no tiene (un buen) lugar en el C ++ moderno a menos que esté templado.


Si tiene un objeto que nunca, nunca debe ser delete o almacenado en la pila (almacenamiento automático), o almacenado como parte de otro objeto, =delete evitará todos estos.

struct Handle { ~Handle()=delete; }; struct Data { std::array<char,1024> buffer; }; struct Bundle: Handle { Data data; }; using bundle_storage = std::aligned_storage_t<sizeof(Bundle), alignof(Bundle)>; std::size_t bundle_count = 0; std::array< bundle_storage, 1000 > global_bundles; Handle* get_bundle() { return new ((void*)global_bundles[bundle_count++]) Bundle(); } void return_bundle( Handle* h ) { Assert( h == (void*)global_bundles[bundle_count-1] ); --bundle_count; } char get_char( Handle const* h, std::size_t i ) { return static_cast<Bundle*>(h).data[i]; } void set_char( Handle const* h, std::size_t i, char c ) { static_cast<Bundle*>(h).data[i] = c; }

Aquí tenemos Handle opacos que no pueden declararse en la pila ni asignarse dinámicamente. Tenemos un sistema para obtenerlos de una matriz conocida.

Yo creo que nada arriba es un comportamiento indefinido; no poder destruir un Bundle es aceptable, como lo es crear uno nuevo en su lugar.

Y la interfaz no tiene que exponer cómo funciona Bundle . Solo una Handle opaca.

Ahora, esta técnica puede ser útil si otras partes del código necesitan saber que todos los Handles están en ese búfer específico, o que su vida útil se rastrea de maneras específicas. Posiblemente esto también podría manejarse con constructores privados y funciones de fábrica de amigos.


un escenario podría ser la prevención de la desasignación incorrecta:

#include <stdlib.h> struct S { ~S() = delete; }; int main() { S* obj= (S*) malloc(sizeof(S)); // correct free(obj); // error delete obj; return 0; }

esto es muy rudimentario, pero se aplica a cualquier proceso especial de asignación / desasignación (por ejemplo, una fábrica)

un ejemplo de estilo más ''c ++''

struct data { //... }; struct data_protected { ~data_protected() = delete; data d; }; struct data_factory { ~data_factory() { for (data* d : data_container) { // this is safe, because no one can call ''delete'' on d delete d; } } data_protected* createData() { data* d = new data(); data_container.push_back(d); return (data_protected*)d; } std::vector<data*> data_container; };


¿Por qué marcar un destructor como delete ?

Para evitar que el destructor sea invocado, por supuesto;)

¿Cuáles son los casos de uso?

Puedo ver al menos 3 usos diferentes:

  1. La clase nunca debería ser instanciada; en este caso, también esperaría un constructor predeterminado eliminado.
  2. Una instancia de esta clase debe filtrarse; por ejemplo, una instancia de singleton de registro
  3. Una instancia de esta clase solo puede ser creada y eliminada por un mecanismo específico; esto podría ocurrir notablemente al usar FFI

Para ilustrar el último punto, imagine una interfaz C:

struct Handle { /**/ }; Handle* xyz_create(); void xyz_dispose(Handle*);

En C ++, querría envolverlo en un unique_ptr para automatizar el lanzamiento, pero ¿qué unique_ptr<Handle> si accidentalmente escribe: unique_ptr<Handle> ? ¡Es un desastre en tiempo de ejecución!

Entonces, en cambio, puedes modificar la definición de clase:

struct Handle { /**/ ~Handle() = delete; };

y luego el compilador se obstruirá en unique_ptr<Handle> forzándolo a usar correctamente unique_ptr<Handle, xyz_dispose> lugar.