Perché il distruttore viene ignorato in questo codice?
-
19-08-2019 - |
Domanda
Il codice seguente mostra uno strano problema che ho in un progetto Explorer Turbo C ++. Uno dei tre oggetti dello stack in D :: D () non viene distrutto dopo essere uscito dall'ambito.
Ciò accade solo se compilato in modalità di rilascio, gli auto_ptrs a_ e b_ sono di tipi diversi e l'eccezione generata non eredita da std :: exception. Sembra funzionare bene in VC ++ 2005 e C ++ Builder 2009. Ho installato BDS2006 Update 2, l'aggiornamento rapido e l'aggiornamento rapido 12.
È il mio codice o il compilatore? Conosci una soluzione? Non essere in grado di utilizzare in modo affidabile auto_ptr in un progetto VCL sarebbe piuttosto scomodo.
#include <memory>
#include <stdexcept>
#include <iostream>
typedef std::exception my_error; // will work fine if replaced with line below
//class my_error : public std::exception {};
class A {};
class B {};
class C
{
public:
C(int id) : id_(id) { std::cout << "C::C() " << id_ << std::endl; };
~C() { std::cout << "C::~C() " << id_ << std::endl; };
private:
int id_;
};
class D
{
public:
D()
{
C c1(1);
C c2(2);
C c3(3);
throw my_error();
};
private:
std::auto_ptr<A> a_;
std::auto_ptr<B> b_; // will work fine if replaced with line below
// std::auto_ptr<A> b_;
// std::auto_ptr<C> c_; // see expected output
};
#pragma argsused
int main(int argc, char* argv[])
{
try
{
D d;
}
catch (...)
{
std::cout << "caught exception" << std::endl;
}
return 0;
}
Previsto:
C::C() 1 C::C() 2 C::C() 3 C::~C() 3 C::~C() 2 C::~C() 1 caught exception
Got:
C::C() 1 C::C() 2 C::C() 3 C::~C() 2 C::~C() 1 caught exception
Ottenuto (con la riga '// std::auto_ptr<C> c_;
' non commentata):
C::C() 1 C::C() 2 C::C() 3 C::~C() 1 caught exception
Modifica: apportate modifiche suggerite
Modifica 2:
Ho appena provato con C ++ Builder 2007 (11.0.2902.10471), che mostra lo stesso problema. La configurazione della versione funziona non appena controllo la quot &; Informazioni di debug & Quot; casella nel progetto - > Opzioni - & Gt; Compilatore C ++ - & Gt; Debug. Mi sorprende che l'eseguibile si riduca con & Quot; Informazioni di debug & Quot; abilitato (fino a 31,5 KB da 39,5 KB).
Modifica 3:
In Turbo C ++ Explorer (C ++ Builder 2006) (10.0.2288.42451) la configurazione di rilascio funziona se deseleziono la & Quot; Espansione della funzione in linea (-vi) & Quot; casella nel progetto - > Opzioni - & Gt; Compilatore C ++ - & Gt; Debug. La sostituzione della prima riga (#include <memory>
) con il seguente codice fa funzionare anche questa.
#pragma option push -vi-
#include <memory>
#pragma option pop
Soluzione
Questo sembra essere un bug del compilatore. Ho appena eseguito lo stesso campione in VS2008SP1 e ho ottenuto l'output previsto.
Altri suggerimenti
Per qualunque cosa valga, GCC 3.4.6 fa la cosa prevista:
$ g++ main.cpp
$ a.out
C::C()
C::C()
C::~C()
C::~C()
caught exception
È un bug del compilatore in C ++ Builder 2006. C ++ Builder 2009 lo risolve; questo è l'output che ottengo per BCC v6.1:
C::C() 1
C::C() 2
C::C() 3
C::~C() 3
C::~C() 2
C::~C() 1
caught exception
Se viene generata un'eccezione in un costruttore di oggetti, il distruttore non verrà eseguito.
Il compilatore non ha modo di sapere se il costruttore è stato completato sufficientemente per consentire al distruttore di funzionare correttamente.
Vedi http://www.parashift.com/ C ++ - faq-lite / exceptions.html # faq-17,4
EDIT: rispondendo al commento qui sotto ... In questo caso, molto probabilmente è un bug del compilatore che contiene la regola "non eseguire il distruttore" con non distruggere erroneamente oggetti nello stack.
Forse il flusso di cout non è svuotato? Puoi provare con cerr invece? o mettere direttamente un breakpoint nel distruttore e verificare se vengono colpiti?
Ho appena provato questo sulla riga di comando gratuita bcc5.5.1 e C ++ Builder 6 bcc5.64 ed entrambi funzionano come previsto - il che è una sorpresa considerando quanti anni hanno. Poi ho provato questo in C ++ Builder 2007, bcc5.93 e il bug è presente lì. In effetti, il codice di esempio potrebbe essere semplificato ai tipi primitivi e il bug sarebbe ancora presente:
class D
{
public:
D();
private:
std::auto_ptr<int> a_;
std::auto_ptr<short> b_;
std::auto_ptr<char> c_;
std::auto_ptr<bool> d_;
};
Questo esempio estremo finisce per causare la chiamata di nessuno dei distruttori corrispondenti per la classe C! Se sei interessato a diagnosticare ulteriormente questo errore, un trucco che puoi eseguire è quello di inserire un punto di interruzione dell'assemblaggio nel tuo ctor D :: D ():
// Note that D::D() ctor can't be inlined if it contains assembly
// limitation of borland compilers unfortunately
D::D()
{
__asm int 3;
C c1(1);
C c2(2);
C c3(3);
throw my_error();
}
Dovresti quindi lasciarlo scorrere attraverso il debugger. Quando l'esecuzione raggiunge quel punto di interruzione specificato, il programma si arresterà e il controllo tornerà al debugger. È quindi possibile passare attraverso l'assemblaggio per vedere dove si trova il problema.
Sembra un bug nel codice di svolgimento dello stack di gestione delle eccezioni. Prova a creare una semplice classe E con un'istanza di essa nel costruttore di D e vedi se viene chiamata.