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=



  • drakon schrieb:

    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?

    Ne, ich seh gerade, dass die Frage auch etwas schlecht gestellt ist, weil sie nur im Zusammenhang mit dem Threadtitel Sinn macht. Es geht um Beispiele wozu man exceptionsichere assignment Operatoren braucht, um dann noch das korrekte alte Objekt zu haben, obwohl eine Exception beim Zuweisen geflogen ist.

    Mir ist ein Beispiel eingefallen das möglich wäre, aber dass ich auch nicht für sehr realistisch halte.
    Man möchte z.B. eine state machine die immer den alten Zustand behält, wenn man den neuen Zustand nicht schalten kann.

    try
    {
        m_currentState = m_stateA;
    }
    catch(...)
    {
       log("State change failed, old state remains. ")
    }
    

    Aber warum sollte man immer ein ganzes Objekt kopieren um einen State zu schalten. Einfach nur nen Pointer umsetzen wäre viel performanter und dabei kann auch keine Exception fliegen. Und bei welchem Anwendungsfall will man schon immer den alten State behalten?

    Die einzigen Sachen die mir einfallen, die exceptionsichere assignment Operatoren haben sollten, sind Libraries die praktisch für alles verwendet werden können. Da könnte ja mal jemand ein Anwendungsfall einfallen, wo er das braucht. Was aber nicht meine Frage nach so einem Anwendungsfall beantwortet. 😃

    pumuckl schrieb:

    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=

    Du hast also eine Operation bei der du ein Objekt übergibst und weist dem was zu und wenn eine Exception vor oder während dem Zuweisen fliegt, willst du den alten Zustand loggen, wenn die Exception später fliegt, willst du den neu zugewiesenen Zustand loggen? Oder meinst du was anderes? Mach einfach ein Codebeispiel.


Anmelden zum Antworten