Newsgroup: it.comp.lang.c++
Titolo: Ma se il costruttore fallisce?

Cioè se nel costruttore, mentre alloco alcune risorse, la memoria non
bastasse? Che succede?
L'oggetto non viene istanziato?

 L'oggetto non viene istanziato?
1 As control passes from a throw-expression to a handler, destructors
are invoked for all automatic objects constructed since the try block
was entered. The automatic objects are destroyed in the reverse order of
the completion of their construction.
2 An object that is partially constructed or partially destroyed will
have destructors executed for all of its fully constructed subobjects,
that is, for subobjects for which the constructor has completed
execution and the destructor has not yet begun execution. Should a
constructor for an element of an automatic array throw an exception,
only the constructed elements of that array will be destroyed. If the
object or array was allocated in a new-expression and the new-expression
does not contain a new-placement, the deallocation function (3.7.3.2,
12.5) is called to free the storage occupied by the object; the
deallocation function is chosen as specified in 5.3.4. If the object or
array was allocated in a new- expression and the new-expression contains
a new-placement, the storage occupied by the object is deallocated only
if an appropriate placement operator delete is found, as specified in 5.3.4.
3 The process of calling destructors for automatic objects constructed
on the path from a try block to a throw- expression is called “stack
unwinding.” [Note: If a destructor called during stack unwinding exits
with an exception, terminate is called (15.5.1). So destructors should
generally catch exceptions and not let them propagate out of the
destructor. —end note]

Se non ho capito male, il punto 1 dice che non appena il controllo
passa da un'espressione throw ad un handler, viene richiamato il
distruttore per tutte quegli oggetti automatici costruiti da quando si
è entrati nel blocco try. Gli oggetti automatici vengono distrutti
nell'ordine contrario a quello incontrato nel blocco try.
Ma questo comporta l'esecuzione del distruttore scritto da me? O
vengono solo liberate le risorse?
Io per fare una prova ho costruito un oggetto che tenta di allocare
tre buffer da 1GB l'uno.
Al secondo tentativo il processo crepa. Io intercetto l'errore ma
nella main() mi risulta comunque istanziato l'oggetto, che però non
potrebbe lavorare in quanto senza risorse.
Ecco il sorgente:
#include <iostream>
#define KB    1024
#define MB 1024*KB
#define GB 1024*MB
class CTest
{
  public:
    CTest();
   ~CTest();
    int can();
  private:
    char *buf, *buf2, *buf3;
    int level;
};
CTest::CTest()
{
  std::cout << "Costrutture di CTest(). Provo ad allocare le risorse."
<< std::endl;
  buf = buf2 = buf3 = NULL;
  level = 0;
  try
  {
    buf = new char[1*GB];
    level++;
    buf2 = new char[1*GB];
    level++;
    buf3 = new char[1*GB];
    level++;
    if (!buf || !buf2 || !buf3) throw(1);
  }
  catch (...)
  {
    std::cout << "Errore :(" << std::endl;
  }
}
CTest::~CTest()
{
  std::cout << "Eliminazione di CTest()" << std::endl;
  if (buf) delete[] buf;
  if (buf2) delete[] buf2;
  if (buf3) delete[] buf3;
}
int CTest::can()
{
  return level;
}
int main()
{
  CTest *test = NULL;
  test = new CTest;
  if (test)
  {
    std::cout << "Oggetto inizializzato correttamente" << std::endl;
    std::cout << "Test: " << test->can() << std::endl;
    delete test;
  }
  else
    std::cout << "Oggetto non inizializzato" << std::endl;
  return 1;
}
E l'output è:
Costrutture di CTest(). Provo ad allocare le risorse.
Errore :(
Oggetto inizializzato correttamente
Test: 1
Eliminazione di CTest()
Io vorrei fare in modo da ottenere NULL quando faccio new CTest().
Dove sbaglio?

 Dove sbaglio?
Il tuo costruttore viene eseguito correttamente giacch� le eccezioni vengono
intercettate internamente. Non puoi fare in modo che una chiamata a new
restituisca NULL. Puoi, invece, lasciare uscire un'eccezione dal
costruttore, ma devi assicurarti che gli oggetti non automatici instanziati
vengano distrutti correttamente. Questo, normalmente, si ottiene con
auto_ptr, o costrutti analoghi.
Max

 Puoi, invece, lasciare uscire un'eccezione dal costruttore
Come in questo caso (che per� non dealloca oggetti non automatici)
#include <iostream>
class CTest {
  public:
    CTest();
  private:
    char *buf, *buf2;
};
CTest::CTest() {
    buf = new char[1073741824];
    buf2 = new char[1073741824];
}
int main() {
  CTest *test = NULL;
  try {
      test = new CTest;
  }
  catch (...)
  {
    std::cout << "Errore :(" << std::endl;
  }
  if (test)
    std::cout << "Oggetto inizializzato correttamente" << std::endl;
  else
      std::cout << "Oggetto non inizializzato" << std::endl;
  return 0;
} 

 Come in questo caso (che però non dealloca oggetti non automatici)
Cioè se nel constructor dell'esempio, la prima chiamata a new fa a
buon fine e la seconda no, resta allocata la prima memoria ottenuta?

 buon fine e la seconda no, resta allocata la prima memoria ottenuta?
Certo. Non c'� nessuno che chiama 'delete[]'. Se vuoi rendere quel codice
exception-safe dovresti usare qualcosa di simile ad auto_ptr, che funzioni
per� con puntatori restituiti da new[]. Ad esempio, boost::scoped_array.
Normalmente, comunque, uno userebbe std::vector, in questi casi.
Max

 bastasse? Che succede?
Dipende. Vediamo il codice.
-- 
-riko

Max M. ha scritto:

Aggiungo giusto come spunto di ricerca: il concetto (giustissimo) che 
Max sta esprimendo � quello che in gergo si chiama RAII, ed � alla base 
del C++ (IMHO � anche uno degli aspetti chiave che fa la differenza tra 
C++ e gli altri linguaggi "managed" e sancisce la "superiorit�" del 
C++...). Giusto per intenderci, l'esistenza del RAII giustifica 
ampiamente l'assenza del costrutto finally (e consente implementazioni 
migliori!).
Se qualcuno si fosse mai posto la domanda "perch� in c++ non c'� 
finally? farebbe comodo!" allora molto probabilmente questo qualcuno non 
conosce bene RAII
Un'altro "indizio" della scarsa padronanza RAII � un uso eccessivo (e 
scorretto) di blocchi try catch (vedi ad esempio il costruttore postato 
da Darkbyte)
Un buono spunto:
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.12
http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.13

 restituisca NULL. 
Non � proprio vero;
occorre usare nothrow e new restituisce NULL se non riesce ad allocare