Domanda

Possibile duplicato:
Quando è utile una funzione Try Block?
Differenza tra la sintassi di prova per la funzione

Questo codice lancia un int eccezione durante la costruzione del Dog oggetto all'interno della classe UseResources. Il int L'eccezione è catturata da un normale try-catch Blocca e le uscite del codice:

Cat()  
Dog()  
~Cat()  
Inside handler

#include <iostream>
using namespace std;

class Cat
{
    public:
    Cat() { cout << "Cat()" << endl; }
    ~Cat() { cout << "~Cat()" << endl; }
};

class Dog
{
    public:
    Dog() { cout << "Dog()" << endl; throw 1; }
    ~Dog() { cout << "~Dog()" << endl; }
};

class UseResources
{
    class Cat cat;
    class Dog dog;

    public:
    UseResources() : cat(), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

int main()
{
    try
    {
        UseResources ur;
    }
    catch( int )
    {
        cout << "Inside handler" << endl;
    }
}

Ora, se sostituiamo la definizione del UseResources() costruttore, con uno che usa un function try block, come sotto,

UseResources() try : cat(), dog() { cout << "UseResources()" << endl; } catch(int) {}

L'output è la stessa

Cat()  
Dog()  
~Cat()  
Inside handler 

cioè, con esattamente lo stesso risultato finale.

Qual è allora, lo scopo di a function try block ?

È stato utile?

Soluzione

Immagina se UseResources è stato definito così:

class UseResources
{
    class Cat *cat;
    class Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { delete cat; cat = NULL; cout << "~UseResources()" << endl; }
};

Se Dog::Dog() lancia, quindi cat Farà la memoria. Becase UseResourcescostruttore mai completato, l'oggetto non è mai stato completamente costruito. E quindi non ha il suo distruttore chiamato.

Per evitare questa perdita, è necessario utilizzare un blocco Try/Catch a livello di funzione:

UseResources() try : cat(new Cat), dog() { cout << "UseResources()" << endl; } catch(...)
{
  delete cat;
  throw;
}

Per rispondere alla tua domanda in modo più completo, lo scopo di un blocco di prova/cattura a livello di funzione nei costruttori è specificamente quello di fare questo tipo di pulizia. Blocchi di prova/cattura a livello di funzione non può Ingregare le eccezioni (quelle normali possono). Se catturano qualcosa, lo lanceranno di nuovo quando raggiungeranno la fine del blocco di cattura, a meno che non lo ritmi esplicitamente throw. Puoi trasformare un tipo di eccezione in un altro, ma non puoi semplicemente inghiottirlo e continuare come non è successo.

Questo è un altro motivo per cui i valori e i puntatori intelligenti dovrebbero essere usati al posto di puntatori nudi, anche come membri della classe. Perché, come nel tuo caso, se hai solo valori dei membri anziché puntatori, non devi farlo. È l'uso di un puntatore nudo (o altra forma di risorsa non gestita in un oggetto RAII) che forza questo genere di cose.

Si noti che questo è praticamente l'unico uso legittimo dei blocchi di prova/cattura della funzione.


Altri motivi per non utilizzare la funzione Prova i blocchi. Il codice sopra è sottilmente rotto. Considera questo:

class Cat
{
  public:
  Cat() {throw "oops";}
};

Quindi, cosa succede in UseResourcescostruttore? Bene, l'espressione new Cat lancerà, ovviamente. Ma questo significa quello cat Non è mai stato inizializzato. Che significa che delete cat produrrà comportamenti indefiniti.

Potresti provare a correggerlo usando un lambda complesso invece di giusto new Cat:

UseResources() try
  : cat([]() -> Cat* try{ return new Cat;}catch(...) {return nullptr;} }())
  , dog()
{ cout << "UseResources()" << endl; }
catch(...)
{
  delete cat;
  throw;
}

Che teoricamente risolve il problema, ma rompe un'invariante presunta di UseResources. Vale a dire, quello UseResources::cat sarà sempre un puntatore valido. Se questo è davvero invariante di UseResources, quindi questo codice fallirà perché consente la costruzione di UseResources Nonostante l'eccezione.

Fondamentalmente, non c'è modo di rendere questo codice a meno che new Cat è noexcept (esplicitamente o implicitamente).

Al contrario, questo funziona sempre:

class UseResources
{
    unique_ptr<Cat> cat;
    Dog dog;

    public:
    UseResources() : cat(new Cat), dog() { cout << "UseResources()" << endl; }
    ~UseResources() { cout << "~UseResources()" << endl; }
};

In breve, guarda un blocco di prova a livello di funzione come a serio odore di codice.

Altri suggerimenti

Funzione ordinaria I blocchi di prova hanno relativamente poco scopo. Sono quasi identici a un blocco di prova all'interno del corpo:

int f1() try {
  // body
} catch (Exc const & e) {
  return -1;
}

int f2() {
  try {
    // body
  } catch (Exc const & e) {
    return -1;
  }
}

L'unica differenza è che la funzione-bloccante vive nella fase di funzione leggermente più ampia, mentre la seconda costruzione vive nella scusa funzione-corpo-il primo ambito vede solo gli argomenti della funzione, il secondo anche le ultime variabili locali (ma Ciò non influisce sulle due versioni dei blocchi di prova).

L'unica applicazione interessante arriva in un costruttore-Troni-blocco:

Foo() try : a(1,2), b(), c(true) { /* ... */ } catch(...) { }

Questo è l'unico modo in cui le eccezioni da uno degli inizializzatori possono essere catturate. Non puoi maneggiare L'eccezione, poiché l'intera costruzione di oggetti deve ancora fallire (quindi è necessario uscire dal blocco di cattura con un'eccezione, che tu voglia o meno). Tuttavia, esso è L'unico modo per gestire le eccezioni dall'elenco inizializzatore in modo specifico.

È utile? Probabilmente no. Non c'è essenzialmente nessuna differenza tra un blocco di prova del costruttore e il seguente modello "inizializza a null-e-assernio" più tipico, che è esso stesso terribile:

Foo() : p1(NULL), p2(NULL), p3(NULL) {
  p1 = new Bar;
  try {
    p2 = new Zip;
    try {
      p3 = new Gulp;
    } catch (...) {
      delete p2;
      throw;
    }
  } catch(...) {
    delete p1;
    throw;
  }
}

Come puoi vedere, hai un disordine insignificante e non scalabile. Un blocco di tempesta di costruttore sarebbe ancora peggio perché non si può nemmeno dire quanti dei puntatori siano già stati assegnati. Quindi davvero lo è solo utile se hai precisamente Due allocazioni tragibili. Aggiornare: Grazie alla lettura questa domanda Sono stato avvisato del fatto che in realtà Non è possibile utilizzare il blocco di cattura per ripulire le risorse Dal momento che fare riferimento agli oggetti membri è un comportamento indefinito. Quindi [end update

In breve: è inutile.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top