Seltsames Phänomen bei Zuweisungen in Schleife



  • Hallo Leute! 🙂

    Ich habe ein - wie ich finde - sehr seltsames Problem.
    Ich möchte zur Übung eine Klasse 'Fraction' schreiben, die einen Dezimalbruch darstellt und entsprechende Rechenoperationen bietet. Das ist mir im Grunde auch gelungen, aber bei deren Verwendung ergibt sich folgende Schwierigkeit:

    Dieser Code

    Fraction* f[2];
    	string s;
    	cin >> s;
    	try{ 
    		f[0] = &Fraction::parseFraction(s); //interpretiert den String und erzeugt ein Fraction-Objekt
    	} catch (exception& ex) {
    		cout << "An error ocurred: " << ex.what() << endl;
    	}
    	cin >> s;
    	try{ 
    		f[1] = &Fraction::parseFraction(s);
    	} catch (exception& ex) {
    		cout << "An error ocurred: " << ex.what() << endl;
    	}
    

    liest nacheinander zwei strings aus der Konsole ein und konvertiert sie jeweils in einen Bruch (Fraction-Objekt) und funktioniert prächtig.

    Dieser Code

    Fraction* f[2];
    	int i=0;
    	while(i<2){
    		string s;
    		cin >> s;
    		try{ 
    			f[i] = &Fraction::parseFraction(s);
    			i++;
    		} catch (exception& ex) {
    			cout << "An error ocurred: " << ex.what() << endl;
    		}
    	}
    

    sollte meines Wissens nach genau dasselbe tun, liefert aber im Gegensatz zum obigen das Resultat, dass am Ende 'f[0]' und 'f[1]' identisch sind und dem zweiten eingegebenen Bruch entsprechen.

    Kann mir das irgendwer erklären? 😕
    Ich bin absolut ratlos und für jede Hilfe dankbar!

    Grüße
    SuperSeppel13



  • Warum erstellst du denn ein Array aus zwei Zeigern auf Fraction anstatt ein Array aus zwei Fraction? Du speicherst hier die Adresse eine temporären Objekts, das kann ja nicht klappen.



  • Also die Variante wie du sie geschildert hast, würde in der parseFraction Methode zum Schluss ein "return *(new Fraction(...))" stehen und das ist extrem hässlich und würde niemand so machen!

    Du brauchst die Funktion so:

    // Fraction.h
    static Fraction Fraction::parseFraction(...)
    {
        // ...
        return Fraction(nom, denom);
    }
    
    //main.cpp
    Fraction fraction[2] = { Fraction::parseFraction(cin.readline()), Fraction::parseFraction(cin.readline()) }; // stimmt das cin.readline()?
    

    So bekommst du eine volle Kopie des Fraction. Du hast nur einem Pointer auf ein bereits wieder zerstörtes Objekt bekommen. Dadurch, dass die Daten nur wieder freigegeben werden (nicht überschrieben) erschien es dir, dass du mit den Objekten arbeitest, aber eigentlich solltest du eine Fehlermeldung bekommen.



  • Danke erstmal für die schnelle Antwort!
    Meine parseFraction-Methode sieht so aus:

    Fraction Fraction::parseFraction(string s) throw (exception) {
    	stringstream ss(s);
    	int num_, denom_;
    	char c;
    	bool error = false;
    
    	error |= (ss>>num_).fail();// <-- hier werden die zwei zahlen eingelesen...
    	ss>>c;
    	error |= c != '/';
    	error |= (ss>>denom_).fail();// <--
    
    	if(error){
    		throw exception(("Invalid input: " + s).c_str());
    	}else{
    		return Fraction(num_, denom_);//...und hier dann das Fraction-Objekt erzeugt
    	}
    }
    

    Also eigentlich war das schon so, wie du vorgeschlagen hast.
    Und 'cin.readline()' gibt's in meiner welt nicht (?), aber das einlesen der strings ist auch nicht das problem.

    Ich hätte halt vor allem gedacht, dass die beiden Code-Beispiele, die ich gepostet hab, exakt gleichwertig sind und ich kann mir nicht erklären, warum das eine funktioniert und das andere nicht.
    Irgendeine Idee dazu...?



  • SuperSeppel13 schrieb:

    Irgendeine Idee dazu...?

    Liest eigentlich niemand meine Beiträge? Dann kann ich sie mir ja auch sparen. 🙄



  • Ah, den hab ich total übersehen - tut mir voll Leid 😕

    Ja, so klappt's. Ich hatte das zuerst nicht so bemacht, weil Fraction keinen parameterlosen Konstruktor hatte und deswegen die Initialisierung des Array nicht funktioniert hat.

    Also erstmal vielen Dank! 🙂

    Ganz so klar ist mir allerdings nicht, warum das vorher bei der einen Version funktioniert hat und bei der anderen nicht.
    Ist da das erste Objekt direkt wieder zerstört worden und dann das andere einfach an derselben stelle im Speicher gelandet? Aber warum dann nur bei der Version mit Schleife?!



  • SuperSeppel13 schrieb:

    Ich hatte das zuerst nicht so bemacht, weil Fraction keinen parameterlosen Konstruktor hatte und deswegen die Initialisierung des Array nicht funktioniert hat.

    Falls du das Problem noch mal hast, nutze new zusammen mit std::unique_ptr. (C++11) Der gibt den Speicher automatisch wieder frei und ist dank Destruktor auch Exception-sicher. (Falls dir dynamische Speicherverwaltung noch nicht geläufig ist, pass auf das du das Problem gar nicht erst bekommst. :p)

    SuperSeppel13 schrieb:

    Ganz so klar ist mir allerdings nicht, warum das vorher bei der einen Version funktioniert hat und bei der anderen nicht.
    Ist da das erste Objekt direkt wieder zerstört worden und dann das andere einfach an derselben stelle im Speicher gelandet? Aber warum dann nur bei der Version mit Schleife?!

    Beide Versionen hatten undefiniertes Verhalten, dass überhaupt etwas Sinnvolles herumgekommen ist, war nur Zufall.
    Wird eine Funktion mit Rückgabewert aufgerufen, liefert diese ein temporäres Objekt zurück. Das wird am Ende des Ausdrucks aber sofort wieder ungültig, seine Adresse zu speichern und nachher wieder zu verwenden ist also keine gute Idee.

    create_foo(); // Erstellt temporäres foo
    foo foo1 = create_foo(); // Ok, Objekt wird kopiert
    some_function(create_foo()); // Ok, Objekt wird kopiert
    some_function(&create_foo()); // Ok, Objekt existiert während des Funktionsaufrufs.
    foo const* foo2 = &create_foo(); // "Ok", aber pass auf
    foo2->whatever(); // Autsch! Das Objekt auf das der Pointer zeigt existiert nicht mehr!
    


  • Wunderbar! Wieder was gelernt. 🙂
    Den unique_ptr schau ich mir auch mal an...

    Nochmal ein großes Dankeschön!



  • Wobei du bei solchen Sachen aufpassen solltest. Ist etwas verzwickt wie mir scheint: http://accu.org/index.php/journals/227
    (Betrifft insbesondere:)

    some_function(&create_foo());
    foo* foo2 = &create_foo();
    

    Das würde mit Built-In Typen nicht funktionieren, und scheint mir daher irgendwie inkonsistent. Es kompiliert allerdings: http://ideone.com/QbPrI
    Unter VS10 mit

    warning C4238: nonstandard extension used : class rvalue used as lvalue

    Ich weiß aber gerade nicht, ob diese Warnung so noch aktuell ist.

    Am besten du lässt den Adressoperator von temporären Objekten, das macht eh in den seltensten Fällen Sinn. 😉


Anmelden zum Antworten