Wozu braucht man exceptionsichere assignment Operatoren?



  • Hat jemand ein realistisches Beispiel, bei dem er nach einer Exception noch mit dem Objekt weiter arbeitet?



  • Meistens gehts nicht ums weiterarbeiten, sondern darum, einen definierten Zustand zu haben, in dem man die Objekte wieder sauber abräumen kann.


  • Mod

    Jeder Fall wo überhaupt mit Exceptions gearbeitet wird? Exceptions sind zur Fehlerbehandlung und nicht bloß dazu, eine Fehlermeldung anzuzeigen. Und dabei ist es doch recht nützlich, wenn das alte Objekt noch seinen Ursprungszustand hat.

    Software wird tatsächlich auch in der realen Wirtschaft genutzt (oho, wer hätte das gedacht 😉 ) und da darf ein Programm eben nicht einfach so abstürzen oder Daten verlieren.



  • pumuckl schrieb:

    Meistens gehts nicht ums weiterarbeiten, sondern darum, einen definierten Zustand zu haben, in dem man die Objekte wieder sauber abräumen kann.

    Klingt eigentlich logisch, aber inwiefern ist dann sicher gestellt, dass die temporäre Kopie sauber abgeräumt werden kann, wenn beim Kopieren eine Exception auftritt (unter der Annahme man verwendet Copy&Swap)?

    SeppJ: Dein Zeug geht ziemlich an meiner Frage vorbei.



  • antenna schrieb:

    pumuckl schrieb:

    Meistens gehts nicht ums weiterarbeiten, sondern darum, einen definierten Zustand zu haben, in dem man die Objekte wieder sauber abräumen kann.

    Klingt eigentlich logisch, aber inwiefern ist dann sicher gestellt, dass die temporäre Kopie sauber abgeräumt werden kann, wenn beim Kopieren eine Exception auftritt (unter der Annahme man verwendet Copy&Swap)?

    Welche temporäre Kopie? Die des Exception Objektes? Die sollte keine Exception werfen können, sonst kann man ein Problem haben ja.

    Wenn es um das werfende Objekt geht, dann ist das ja deine Aufgabe zu sorgen, dass das Objekt, wenn es eine Exception wirft in einem korrekten Zustand ist. Ob es dann korrekt zerstört werden kann ist eine andere Frage (kann es das denn, wen keien Exception geworfen wurde?)



  • antenna schrieb:

    Klingt eigentlich logisch, aber inwiefern ist dann sicher gestellt, dass die temporäre Kopie sauber abgeräumt werden kann, wenn beim Kopieren eine Exception auftritt (unter der Annahme man verwendet Copy&Swap)?

    Naja, ein op= der nur aus Copy&Swap besteht, hat einen Punkt, wo es ne Exception geben kann - im Copy-Teil. Und wenn beim Ctor eine Exception auftritt hat das temporäre Objekt nie existiert. Natürlich muss auch der Copy-Ctor dann exceptionsicher sein.



  • Mach ich was falsch? Ich hab jetzt ein bisschen rum probiert aber für die Objekte die im Copy-Ctor erstellt werden, wird nicht mal mehr der Dtor aufgerufen, wenn die Exception fliegt.

    int n = 0;
    
    class Ex
    {
    public:
    	Ex()
    	{
    		std::cout<<"Ex constr...\n";
    		n++;
    		if(n==4)
    		{
    			std::cout<<"throw\n";
    			throw 1;
    		}
    	}
    	~Ex()
    	{
    		std::cout<<"Ex destr...\n";
    	}
    
    };
    
    class Foo
    {
    public:
    	Foo()
    		: e1( NULL)
    		, e2( NULL)
    	{
    		std::cout<<"Foo constr...\n";
    		e1 = new Ex();
    		e2 = new Ex();
    	}
    
    	Foo( Foo const& of)
    		: e1( NULL)
    		, e2( NULL)
    	{
    		std::cout<<"Foo copy constr...\n";
    		e1 = new Ex();
    		e2 = new Ex();
    	}
    
    	~Foo()
    	{
    		std::cout<<"Foo destr...\n";
    		delete e1;
    		delete e2;
    	}
    private:
    	Ex* e1;
    	Ex* e2;
    };
    
    int main()
    {
    	try
    	{
    		Foo f;
    		Foo f2(f);
    	}
    	catch(...)
    	{
    		std::cout<<"catch\n";
    	}
    }
    
    }
    

    Foo constr...
    Ex constr...
    Ex constr...
    Foo copy constr...
    Ex constr...
    Ex constr...
    throw
    Foo destr...
    Ex destr...
    Ex destr...
    catch



  • antenna schrieb:

    Mach ich was falsch? Ich hab jetzt ein bisschen rum probiert aber für die Objekte die im Copy-Ctor erstellt werden, wird nicht mal mehr der Dtor aufgerufen, wenn die Exception fliegt.

    Wenn im Ctor eine Exception fliegt wurde das Objekt nie komplett konstruiert -> es gibt auch nichts wofür man einen Dtor aufrufen kann. Die Lebenszeit eines Objektes beginnt eben erst bei Verlassen des Ctors und endet bei Eintritt in den Dtor.



  • antenna schrieb:

    Mach ich was falsch? Ich hab jetzt ein bisschen rum probiert aber für die Objekte die im Copy-Ctor erstellt werden, wird nicht mal mehr der Dtor aufgerufen, wenn die Exception fliegt.

    Die Objekte (e1, e2) wurden mit new erstellt, liegen also nicht im automatischen Speicherbereich, erwarte keine Wunder 😉
    Dass der zweite Foo-Destruktor nicht aufgerufen wird liegt daran, dass noch f2 ja noch nicht fertig konstruiert war.



  • Dann bringt Copy&Swap beim Aufräumen aber auch nicht viel, sondern lässt das Problem im Copy-Ctor.



  • antenna schrieb:

    Dann bringt Copy&Swap beim Aufräumen aber auch nicht viel, sondern lässt das Problem im Copy-Ctor.

    Irgendwo muss die Verantwortung liegen. Copy&Swap bedeutet: hier liegt sie nicht.

    Aber im Prinzip ists recht trivial: RAII und gut ist.



  • Mit RAII ist aber das Aufräumen auch ohne Copy&Swap kein großes Problem mehr.

    Und was machst du außerdem, wenn du eine 3rd Party Klasse als Member hast, aber diese kein non-throwing swap anbietet?



  • antenna schrieb:

    Mit RAII ist aber das Aufräumen auch ohne Copy&Swap kein großes Problem mehr.

    RAII und Copy&Swap haben nichts miteinander zu tun. swap erlaubt dir commits zu haben, was essentiell für exception sicheren code ist. unabhängig ob du raii hast oder nicht. raii dagegen räumt auf.

    implementiere mal einen komplexen operator= nur mit raii ohne swap. ist sobald du 2 member hast wo eine zuweisen werfen kann nicht mehr trivial möglich.

    bsp:

    class Person {
      string vorname;
      string nachname;
     //...
      Person& operator=(Person const& other) {
        nachname = other.nachname;
        try {
          vorname = other.vorname;
        } catch(...) {
          //was nun??
          throw;
        }
      }
    };
    

    Und was machst du außerdem, wenn du eine 3rd Party Klasse als Member hast, aber diese kein non-throwing swap anbietet?

    ragequitten.

    wenn das wirklich der fall ist, dann muss man diese klasse in einen scoped_ptr packen und hoffen dass das irgendwer mal patcht.

    es gibt keinen grund warum man kein swap anbieten könnte wenn das objekt kopierbar ist. deshalb kann man es meistens trivial nachpatchen. wenn das nicht geht, dann eben scoped_ptr.



  • Shade Of Mine schrieb:

    antenna schrieb:

    Mit RAII ist aber das Aufräumen auch ohne Copy&Swap kein großes Problem mehr.

    RAII und Copy&Swap haben nichts miteinander zu tun. swap erlaubt dir commits zu haben, was essentiell für exception sicheren code ist. unabhängig ob du raii hast oder nicht. raii dagegen räumt auf.

    implementiere mal einen komplexen operator= nur mit raii ohne swap. ist sobald du 2 member hast wo eine zuweisen werfen kann nicht mehr trivial möglich.

    ...

    Ich weiß worum es geht und darum auch meine Ausgangsfrage: "Hat jemand ein realistisches Beispiel, bei dem er nach einer Exception noch mit dem Objekt weiter arbeitet?" Das einzige Beispiel was bis jetzt kam war "die Objekte wieder sauber abräumen", was aber nur in den Copy-Ctor mit RAII verschoben wird. Und meine Ausgangsfrage ist immer noch offen.



  • antenna schrieb:

    Ich weiß worum es geht und darum auch meine Ausgangsfrage: "Hat jemand ein realistisches Beispiel, bei dem er nach einer Exception noch mit dem Objekt weiter arbeitet?" Das einzige Beispiel was bis jetzt kam war "die Objekte wieder sauber abräumen", was aber nur in den Copy-Ctor mit RAII verschoben wird. Und meine Ausgangsfrage ist immer noch offen.

    exception im operator= oder generell?
    der operator= ist natürlich etwas sehr kritisches, wenn der fehlschlägt ist die chance dass du mit dem objekt noch viel machen kannst eher gering. denn der grund warum ein op= fehlschlägt ist idr ein gravierender.

    weiter arbeiten muss man aber aufjedenfall im dtor. das ist immer der fall. meistens macht man dann ein rollback der letzten aktionen (copy&swap ftw).

    aber ich verstehe generell nicht warum du dich so darauf versteifst. imho ist das einfach eine komplett falsche fragestellung die am kern des themas komplett vorbei geht. beispiele für einen op= der fehlschlägt zu liefern ist nicht das problem, das problem ist dein verständnis eines "weiter arbeitens".

    stell dir einfach ein objekt vor, dass du serialisierst und du willst es kopieren. irgendwas geht dabei schief und du musst es nachher natürlich wieder serialisieren. wenn da das objekt in keinem gültigen zustand ist, ist deine anwendung bis zur nächsten neuinstallation defekt.



  • Shade Of Mine schrieb:

    stell dir einfach ein objekt vor, dass du serialisierst und du willst es kopieren. irgendwas geht dabei schief und du musst es nachher natürlich wieder serialisieren. wenn da das objekt in keinem gültigen zustand ist, ist deine anwendung bis zur nächsten neuinstallation defekt.

    In Code übersetzt würde das z.B. so aussehen.

    Objekt o = load();
    	Objekt newO = getNew();
    	try 
    	{
    		o = newO;
    	}
    	catch(...) 
    	{
    	}
    	save(o); //Dank Copy&Swap können wir hier speichen, da wir auf jeden Fall ein gültiges Objekt haben, egal ob in der Zuweisung was scheif ging.
    

    Aber wer programmiert denn so?
    Eigentlich programmiert man doch so, dass garnicht erst gespeichert wird, wenn was schief ging.

    Objekt o = load();
    	Objekt newO = getNew();
    	o = newO; // Wenn hier eine Exception fliegt, wird save nie aufgerufen
    	save(o);
    


  • Was ist aber, wenn du z.B eine sinnvolle Loginausgabe machen willst des Objektes, welches eine Exception geworfen hat?
    Oder um bei dem Beispiel des Speicherns zu bleiben: Du bearbeitest z.B gerade ein Dokument und irgendwas geht schief und aus irgendeinem Grund muss das Programm beendet werden. Jetzt kannst du schön den Fehler abfangen und kurz speichern oder mal eben den User fragen, was er machen möchte.



  • drakon schrieb:

    Was ist aber, wenn du z.B eine sinnvolle Loginausgabe machen willst des Objektes, welches eine Exception geworfen hat?

    Ja, logging des alten Zustandes kann nur funktionieren, wenn man nen exceptionsicheren assignment Operator hat. Aber sein mal ehrlich, wie oft hast du nur um eine Zuweisung ein try-catch gemacht, um dann zu loggen?

    Oder um bei dem Beispiel des Speicherns zu bleiben: Du bearbeitest z.B gerade ein Dokument und irgendwas geht schief und aus irgendeinem Grund muss das Programm beendet werden. Jetzt kannst du schön den Fehler abfangen und kurz speichern oder mal eben den User fragen, was er machen möchte.

    Das "irgendwas geht schief" ist schon wieder viel zu ungenau. Es geht ja um Exceptions im Assignment Operator. Mach doch mal ein realistisches Codebeispiel.



  • Shade hat ja bereits etwas zu dem Operator gesagt:

    Shade Of Mine schrieb:

    der operator= ist natürlich etwas sehr kritisches, wenn der fehlschlägt ist die chance dass du mit dem objekt noch viel machen kannst eher gering. denn der grund warum ein op= fehlschlägt ist idr ein gravierender.

    Ich bin daher von der grundsätzlichen Variante ausgegangen..

    Realistisch ist imo so etwas:

    Object o;
    try
    {
      //...
      o.try_something_that_might_throw ();
      //...
    }
    catch (...)
    {
     // ok ging wohl nicht..
     cout << o.debug_information (); // was für den Entwickler
     save_states_to_file ( o.get_states () ); // und was für den User
    }
    

    So in etwa mache ich das auch, wenn ich lokaler etwas behandeln kann.

    Das war ja deine ursprüngliche Frage, oder nicht?



  • antenna schrieb:

    Ja, logging des alten Zustandes kann nur funktionieren, wenn man nen exceptionsicheren assignment Operator hat. Aber sein mal ehrlich, wie oft hast du nur um eine Zuweisung ein try-catch gemacht, um dann zu loggen?

    Nicht um die Zuweisung. Aber um eine andere Operation. Die muss auch exceptionsicher sein. Und das geht nur wenn die verwendeten Operationen exceptionsicher sind, unter anderem die verwendeten op=


Anmelden zum Antworten