como - Destructor C++ con retorno
c++ destructor (11)
[D] es explícitamente regresar del destructor significa que no queremos destruirlo?
No. Un retorno anticipado (a través de return;
o throw ...
) solo significa que el resto del cuerpo del destructor no se ejecuta. La base y los miembros siguen destruidos y sus destructores aún se ejecutan, consulte [except.ctor] / 3 .
Para un objeto de tipo de clase de cualquier duración de almacenamiento cuya inicialización o destrucción finaliza con una excepción, se invoca el destructor para cada uno de los objetos secundarios completamente construidos del objeto ...
Vea a continuación ejemplos de código de este comportamiento.
Quiero hacer que un determinado objeto se destruya solo a través de otro destructor de objetos, es decir, solo cuando el otro objeto esté listo para ser destruido.
Parece que la pregunta tiene sus raíces en el tema de la propiedad. Eliminar el objeto "propiedad" solo una vez que el elemento primario se destruye en un idioma muy común y se logra con uno de (pero no limitado a);
- Composición, es una variable de miembro automática (es decir, "basada en pila")
- Un
std::unique_ptr<>
para expresar la propiedad exclusiva del objeto dinámico - Un
std::shared_ptr<>
para expresar la propiedad compartida de un objeto dinámico
Dado el ejemplo de código en el OP, std::unique_ptr<>
puede ser una alternativa adecuada;
class Class1 {
// ...
std::unique_ptr<Class2> myClass2;
// ...
};
Class1::~Class1() {
myClass2->status = FINISHED;
// do not delete, the deleter will run automatically
// delete myClass2;
}
Class2::~Class2() {
//if (status != FINISHED)
// return;
// We are finished, we are being deleted.
}
Noto la verificación de la condición si en el código de ejemplo. Sugiere que el estado está vinculado a la propiedad y la duración. No son todos lo mismo; seguro, puede vincular el objeto que alcanza un cierto estado con su duración "lógica" (es decir, ejecutar algún código de limpieza), pero evitaría el enlace directo a la propiedad del objeto. Puede ser una mejor idea reconsiderar algunas de las semánticas involucradas aquí, o permitir que la construcción y destrucción "natural" dicte los estados de inicio y fin del objeto.
Nota al margen ; si tiene que verificar algún estado en el destructor (o afirmar alguna condición final), una alternativa al throw
es llamar a std::terminate
(con algún registro) si esa condición no se cumple. Este enfoque es similar al comportamiento estándar y al resultado cuando se lanza una excepción al desenrollar la pila como resultado de una excepción ya lanzada. Este es también el comportamiento estándar cuando un std::thread
sale con una excepción no controlada.
[D] es explícitamente regresar del destructor significa que no queremos destruirlo?
No (ver arriba). El siguiente código demuestra este comportamiento; vinculado aquí y una versión dinámica . El noexcept(false)
es necesario para evitar que se llame std::terminate()
.
#include <iostream>
using namespace std;
struct NoisyBase {
NoisyBase() { cout << __func__ << endl; }
~NoisyBase() { cout << __func__ << endl; }
NoisyBase(NoisyBase const&) { cout << __func__ << endl; }
NoisyBase& operator=(NoisyBase const&) { cout << __func__ << endl; return *this; }
};
struct NoisyMember {
NoisyMember() { cout << __func__ << endl; }
~NoisyMember() { cout << __func__ << endl; }
NoisyMember(NoisyMember const&) { cout << __func__ << endl; }
NoisyMember& operator=(NoisyMember const&) { cout << __func__ << endl; return *this; }
};
struct Thrower : NoisyBase {
Thrower() { cout << __func__ << std::endl; }
~Thrower () noexcept(false) {
cout << "before throw" << endl;
throw 42;
cout << "after throw" << endl;
}
NoisyMember m_;
};
struct Returner : NoisyBase {
Returner() { cout << __func__ << std::endl; }
~Returner () noexcept(false) {
cout << "before return" << endl;
return;
cout << "after return" << endl;
}
NoisyMember m_;
};
int main()
{
try {
Thrower t;
}
catch (int& e) {
cout << "catch... " << e << endl;
}
{
Returner r;
}
}
Tiene la siguiente salida;
NoisyBase
NoisyMember
Thrower
before throw
~NoisyMember
~NoisyBase
catch... 42
NoisyBase
NoisyMember
Returner
before return
~NoisyMember
~NoisyBase
En C ++ si definimos un destructor de clase como:
~Foo(){
return;
}
al llamar a este destructor, el objeto de Foo
será destruido o si regresa explícitamente del destructor significa que no queremos destruirlo nunca.
Quiero hacer que un determinado objeto se destruya solo a través de otro destructor de objetos, es decir, solo cuando el otro objeto esté listo para ser destruido.
Ejemplo:
class Class1{
...
Class2* myClass2;
...
};
Class1::~Class1(){
myClass2->status = FINISHED;
delete myClass2;
}
Class2::~Class2(){
if (status != FINISHED) return;
}
Busqué en línea y no pude encontrar una respuesta a mi pregunta. También intenté descifrarlo mediante un código paso a paso con un depurador, pero no puedo obtener un resultado concluyente.
vuelve explícitamente desde el destructor significa que no queremos destruirlo nunca.
No.
El destructor es una función por lo que puede usar la palabra clave return
dentro de él, pero eso no evitará la destrucción del objeto, una vez que se encuentre dentro del destructor ya está destruyendo su objeto, por lo que cualquier lógica que quiera evitarlo tendrá que ocurrir antes
Por alguna razón, intuitivamente creo que su problema de diseño puede resolverse con un shared_ptr
y quizás un eliminador personalizado, pero eso requeriría más información sobre dicho problema.
De acuerdo con el estándar C ++ (12.4 Destructors)
8 Después de ejecutar el cuerpo del destructor y destruir cualquier objeto automático asignado dentro del cuerpo, un destructor para la clase X llama a los destructores para los miembros de datos no-estes no variantes directos de X, los destructores para las clases base directas de X y, si X es el tipo de la clase más derivada (12.6.2), su destructor llama a los destructores para las clases base virtuales de X. Se llama a todos los destructores como si estuvieran referenciados con un nombre calificado, es decir, ignorando cualquier posible destructora de anulación virtual en más clases derivadas. Las bases y los miembros se destruyen en el orden inverso de la finalización de su constructor (véase 12.6.2). Una declaración de retorno (6.6.3) en un destructor podría no regresar directamente al llamador; antes de transferir el control a la persona que llama, se llaman los destructores para los miembros y las bases. Los destructores para elementos de una matriz se llaman en orden inverso a su construcción (véase 12.6).
Por lo tanto, una instrucción de devolución no impide que se destruya el objeto para el que se llama al destructor.
En este caso, podría usar una sobrecarga específica de clase del operador de eliminación. Entonces para ti Class2 podrías hacer algo como esto
class Class2
{
static void operator delete(void* ptr, std::size_t sz)
{
std::cout << "custom delete for size " << sz << ''/n'';
if(finished)
::operator delete(ptr);
}
bool Finished;
}
Luego, si establece el final en verdadero antes de la eliminación, se llamará a la eliminación real. Tenga en cuenta que no lo he probado, acabo de modificar el código que he encontrado aquí http://en.cppreference.com/w/cpp/memory/new/operator_delete
Class1::~Class1()
{
class2->Finished = true;
delete class2;
}
Entonces, como todos los demás señalaron, el return
no es una solución.
Lo primero que agregaría es que generalmente no debería preocuparse por esto. A menos que su profesor lo haya preguntado explícitamente. Sería muy extraño si no puedes confiar en que la clase externa solo elimine tu clase en el momento correcto, y creo que nadie más la está viendo. Si se pasa el puntero, probablemente el puntero será shared_ptr
/ weak_ptr
, y dejará que destruya su clase en el momento correcto.
Pero, oye, es bueno preguntarse cómo podríamos resolver un problema extraño si alguna vez surgiera, si aprendemos algo (¡y no perdamos el tiempo en un plazo!)
Entonces, ¿qué es una solución? Si al menos puedes confiar en que el destructor de Class1 no destruirá tu objeto demasiado pronto, entonces puedes declarar el destructor de Class2 como privado y luego declarar el destructor de Class1 como amigo de Class2, así:
class Class2;
class Class1 {
Class2* myClass2;
public:
~Class1();
};
class Class2 {
private:
~Class2();
friend Class1::~Class1();
};
Class1::~Class1() {
delete myClass2;
}
Class2::~Class2() {
}
Como beneficio adicional, no necesita el indicador de "estado"; lo cual es bueno, si alguien deseaba que esto fuera tan malo, ¿por qué no establecería la bandera de estado en FINISHED
otro lugar y luego llamar a delete
?
De esta forma, tiene la garantía real de que el objeto no puede destruirse en ningún otro lugar que no sea el destructor de Class1.
Por supuesto, el destructor de Class1 obtiene acceso a todos los miembros privados de Class2. Puede que no importe, ¡después de todo, Class2 está a punto de ser destruido de todos modos! Pero si lo hace, podemos conjurar formas aún más enrevesadas para evitarlo; Por qué no. Por ejemplo:
class Class2;
class Class1 {
private:
int status;
Class2* myClass2;
public:
~Class1();
};
class Class2Hidden {
private:
//Class2 private members
protected:
~Class2Hidden();
public:
//Class2 public members
};
class Class2 : public Class2Hidden {
protected:
~Class2();
friend Class1::~Class1();
};
Class1::~Class1() {
delete myClass2;
}
Class2Hidden::~Class2Hidden() {
}
Class2::~Class2() {
}
De esta forma, los miembros públicos seguirán estando disponibles en la clase derivada, pero los miembros privados en realidad serán privados. ~ Class1 solo tendrá acceso a los miembros privados y protegidos de Class2, y a los miembros protegidos de Class2Hidden; que en este caso son solo los destructores. Si necesita mantener un miembro protegido de Class2 protegido del destructor de Class1 ... hay formas, pero realmente depende de lo que esté haciendo.
¡Buena suerte!
No, no puedes evitar que el objeto sea destruido por declaración de devolución, solo significa que la ejecución del cuerpo del dtor terminará en ese punto. Después de eso, aún será destruido (incluidos sus miembros y bases), y la memoria aún será desasignada.
Usted migth lanza excepción.
Class2::~Class2() noexcept(false) {
if (status != FINISHED) throw some_exception();
}
Class1::~Class1() {
myClass2->status = FINISHED;
try {
delete myClass2;
} catch (some_exception& e) {
// what should we do now?
}
}
Tenga en cuenta que es una idea terrible de hecho. Será mejor que reconsideres el diseño, estoy seguro de que debe haber uno mejor.
EDITAR
Cometí un error, lanzar una excepción no detendrá la destrucción de sus bases y miembros, solo hace posible obtener el resultado del proceso del dtor de Class2
. Y lo que podría hacerse con eso aún no está claro.
No. El return
solo significa salir del método, no detiene la destrucción del objeto.
Además, ¿por qué querrías? Si el objeto está asignado en la pila y de alguna manera logró detener la destrucción, el objeto viviría en una parte reclamada de la pila que probablemente se sobreescribirá con la siguiente llamada de función, que puede escribir toda la memoria de objetos y crear un comportamiento indefinido .
Del mismo modo, si el objeto está asignado en el montón y lograste evitar la destrucción, tendrías una pérdida de memoria, ya que el código que llamaba delete
supondría que no necesitaba aferrarse a un puntero al objeto mientras todavía está allí. y tomando la memoria que nadie hace referencia.
Por supuesto no. La llamada explícita de ''return'' es 100% equivalente al retorno implícito después de la ejecución del destructor.
Puede crear un nuevo método para hacer que el objeto "se suicide" y mantener el destructor vacío, por lo que algo como esto hará el trabajo que le gustaría hacer:
Class1::~Class1()
{
myClass2->status = FINISHED;
myClass2->DeleteMe();
}
void Class2::DeleteMe()
{
if (status == FINISHED)
{
delete this;
}
}
Class2::~Class2()
{
}
Todos los objetos basados en la pila en su interior serán destruidos, sin importar cuán pronto return
del destructor. Si olvida delete
objetos asignados dinámicamente, entonces habría una fuga de memoria intencional .
Esta es la idea general de cómo funcionarían los constructores de movimientos . El movimiento CTOR simplemente tomaría la memoria del objeto original. El destructor del objeto original simplemente no puede invocar delete
.
~Foo(){
return;
}
significa exactamente lo mismo que:
~Foo() {}
Es similar a una función void
; llegando al final sin return;
declaración es lo mismo que tener return;
al final.
El destructor contiene acciones que se realizan cuando el proceso de destruir un Foo
ya ha comenzado. No es posible abortar un proceso de destrucción sin abortar todo el programa.