Frage

Mögliches Duplikat:
Wann ist eine Funktion, die versuchen, blockieren nützlich?
Unterschied zwischen Try-Catch-Syntax für die Funktion

Dieser Code wirft eine int Ausnahme beim Erstellen der Dog Objekt in der Klasse UseResources. Das int Ausnahme wird von einem Normalen gefangen try-catch Block und die Codeausgaben:

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;
    }
}

Wenn wir nun die Definition der ersetzen UseResources() Konstruktor mit einem, der a verwendet function try block, wie nachstehend,

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

Der Ausgang ist der gleiche

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

dh mit genau dem gleichen Endergebnis.

Was ist dann der Zweck von a function try block ?

War es hilfreich?

Lösung

Stellen Sie sich vor, wenn UseResources wurde so definiert:

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; }
};

Wenn Dog::Dog() Würfe dann cat wird den Speicher austauschen. Wegen UseResources'S Konstruktor nie abgeschlossen, Das Objekt wurde nie vollständig konstruiert. Und deshalb wird seine Destruktorin nicht aufgerufen.

Um dieses Leck zu verhindern, müssen Sie einen Try/Catch-Block auf Funktionsebene verwenden:

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

Um Ihre Frage ausführlicher zu beantworten, besteht der Zweck eines Versuchs-/Fangblocks auf Funktionsebene in Konstruktoren speziell, um diese Art von Aufräumarbeiten durchzuführen. Funktionsniveau-Versuch/Fangblöcke kann nicht schlucken Ausnahmen (normale können). Wenn sie etwas fangen, werfen sie es wieder, wenn sie das Ende des Fangblocks erreichen, es sei denn, Sie überdenken es explizit mit throw. Sie können eine Art von Ausnahme in eine andere verwandeln, aber Sie können sie nicht einfach schlucken und weitermachen, als würde sie nicht passiert.

Dies ist ein weiterer Grund, warum Werte und intelligente Zeiger anstelle von nackten Zeigern verwendet werden sollten, selbst als Klassenmitglieder. Denn wie in Ihrem Fall müssen Sie dies nicht tun. Es ist die Verwendung eines nackten Zeigers (oder einer anderen Form der Ressource, die in einem Raii -Objekt nicht verwaltet wird), der diese Art von Dingen erzwingt.

Beachten Sie, dass dies so gut wie die einzige legitime Verwendung von Funktionsvermutblöcken ist.


Weitere Gründe, Funktionen nicht zu verwenden, versuchen Sie Blöcke. Der obige Code ist subtil unterbrochen. Bedenken Sie:

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

Also, was passiert in UseResourcesKonstruktor? Nun, der Ausdruck new Cat wird offensichtlich werfen. Aber das bedeutet das cat wurde nie initialisiert. Was bedeutet, dass delete cat wird ein undefiniertes Verhalten ergeben.

Sie könnten versuchen, dies zu korrigieren, indem Sie ein komplexes Lambda anstelle von nur nur verwenden new Cat:

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

Das theoretisch das Problem behebt, aber es bricht eine angenommene Invariante von UseResources. Nämlich das UseResources::cat wird jederzeit ein gültiger Zeiger sein. Wenn das in der Tat eine Invariante von ist UseResources, Dann schlägt dieser Code fehl, weil er den Bau von zulässt UseResources Trotz der Ausnahme.

Grundsätzlich gibt es keine Möglichkeit, diesen Code sicher zu machen, es sei denn new Cat ist noexcept (entweder explizit oder implizit).

Im Gegensatz dazu funktioniert dies immer:

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

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

Kurz gesagt, schauen Sie auf einen Versuch auf Funktionsebene wie a ernst Code -Geruch.

Andere Tipps

Normale Funktionsversuchblöcke haben einen relativ geringen Zweck. Sie sind fast identisch mit einem Versuchsblock im Körper:

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

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

Der einzige Unterschied besteht Dies wirkt sich nicht auf die beiden Versionen von Try -Blöcken aus).

Die einzige interessante Anwendung kommt in einem Konstrukteur-try-block:

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

Nur so können Ausnahmen von einem der Initialisierer gefangen werden. Sie können nicht handhaben Die Ausnahme, da die gesamte Objektkonstruktion noch fehlschlagen muss (daher müssen Sie den Fangblock mit Ausnahme verlassen, ob Sie möchten oder nicht). Wie auch immer, es ist Die einzige Möglichkeit, Ausnahmen aus der Initialisiererliste speziell zu behandeln.

Ist das nützlich? Wahrscheinlich nicht. Es gibt im Wesentlichen keinen Unterschied zwischen einem Konstruktor-Versuchsblock und dem folgenden, typischeren "Initializize-to-Null-and-Sign" -Muster, das selbst schrecklich ist:

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;
  }
}

Wie Sie sehen können, haben Sie ein unerbittliches, unvergleichliches Chaos. Ein Konstruktor-Treue-Block wäre noch schlimmer, weil man nicht einmal sagen konnte, wie viele Zeiger bereits zugewiesen wurden. Also wirklich ist es nur nützlich, wenn Sie genau haben zwei Leckbare Zuordnungen. Aktualisieren: Vielen Dank an das Lesen diese Frage Ich war auf die Tatsache aufmerksam gemacht, dass tatsächlich tatsächlich Sie können den Fangblock nicht verwenden, um Ressourcen aufzuräumen Da es sich überhaupt bezieht, sich auf Mitgliedsobjekte zu beziehen, ist ein undefiniertes Verhalten. Also [Ende Update

Kurz gesagt: Es ist nutzlos.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top