variablen Deklaration und leere Objekte?!



  • 'Tschuldigung für den Titel, aber mir is nix besseres eingefallen. Folgendes Problem:

    Ich hab eine Klasse deren Konstruktor (Klasse(Dateiname)) eine Exception werfen kann - sonst hat die Klasse keinen Konstruktor.
    Nun rufe ich das ganze in einem try-catch Block auf:

    try{
      Klasse instanz(dateiname)
    }
    catch(){
     was auch immer
    }
    instanz.funktion() //compiler heult deklaration fehlt -logisch
    //und weitere Verwendungen der instanz
    ...
    

    die unschönste aber einfachste Variante ist allen Code unter dem try-catch-Block in den try Block zu packen. In Java hab ich einfach oben drüber die Variable mit null deklariert:

    Klasse instanz=null;
    

    Das geht nun nicht. DerCompiler meckert, dass er keinen Konstruktor ohne Parameter kennt oder er '0' nicht zuweisen kann. Wie macht man denn sowas in C++?



  • Wie wär es mit:

    try{
      Klasse instanz(dateiname);
      instanz.funktion();
    }
    catch(){
     was auch immer
    }
    


  • genau das wollte ich vermeiden,
    instanz.funktion() gehört ja nicht wirklich in den try block - praktisch habe ich eine menge code der auf der instanz arbeitet. der müsste komplett in den try block (im Moment hab ich genau das).
    in den try block gehört meiner meinung nach aber nur die anweisung, die die ausnahme wirft?!



  • dermoritz schrieb:

    'Tschuldigung für den Titel, aber mir is nix besseres eingefallen. Folgendes Problem:

    Schon okay, dein Titel ist immerhin um Dimensionen besser als "Brauche Hilfe bei Programm", was hier nicht selten vorkommt... 😉

    dermoritz schrieb:

    in den try block gehört meiner meinung nach aber nur die anweisung, die die ausnahme wirft?!

    Nein - du willst schliesslich nicht mehr mit der Klasse weiterarbeiten, wenn eine Exception geworfen wurde. Das würdest du aber tun, wenn nach dem catch -Block noch Memberfunktionsaufrufe stehen.

    Oder willst du sie etwa im catch reparieren? Da könntest du Zeiger verwenden, musst allerdings sehr vorsichtig sein wegen Memory Leaks und Exceptionsicherheit. In dem Zusammenhang wären auch noch Smart Pointer ( boost::scoped_ptr ) erwähnenswert. Hier mal ein Beispiel mit manueller Speicherverwaltung:

    Klasse* zeiger = 0;
    
    try
    {
        zeiger = new Klasse(...); // bei Fehlschlag automatisches delete
    }
    catch (...)
    {
        // Finde Fehlerursache und erstelle neue Instanz - darf hier nicht werfen
        zeiger = new Klasse(...);
    }
    zeiger->funktion(); // ok, da zeiger gültig sein muss.
    delete zeiger;      // ebenfalls ok.
    


  • vielen Dank genau das wollt ich wissen: ob es üblich ist alles in den try-Block zu packen.
    ich hab den try block auch eher so interpretiert: "versuche etwas und mache danach (nach dem try catch) weiter falls der Versuch geglückt ist"

    in Java konnte man das wie gesagt per "null" regeln - die Lösung mit dem Pointer ist analog.



  • mir ist grad eingefallen, das das Problem viel allgemeiner ist:

    wie kann ich denn allgemein eine Variable die lokal ensteht/definiert wird "nach außen" holen? (außer über Zeiger)

    also Anstelle von try z.B. ein if-Block. Oder andersherum - sollte man bei der Klassendefinition immer einen leeren Konstruktor bereitstellen, der ein "Minimalobjekt" liefert und innerhalb des Blocks überschrieben wird?

    bzw. mit bestimmten Klassen funktioniert die reine Deklaration:

    bestimmteKlasse instanz; //(z.B. QByteArray)
    

    aber bei eigenen Klassen kommt da immer ein Fehler:

    eigeneKlasse instanz; //no matching function for call to `eigeneKlasse::eigeneKlasse()'
    

    wie kann man denn obiges Verhalten erreichen?



  • Was den try-Block angeht:

    Wenn im Konstruktor eine Exception geworfen wurde, dann hat das Objekt nie angefangen zu existieren. Siehs als Abtreibung. Klingt hart, ist aber so. Und für ein Objekt das nie existiert hat kannst du auch keine Methoden aufrufen. Wenn die Konstruktion fehlschlägt ist im catch-block auch nichts mehr zu reparieren.

    dermoritz schrieb:

    wie kann ich denn allgemein eine Variable die lokal ensteht/definiert wird "nach außen" holen? (außer über Zeiger)

    Außer über Zeiger nur, indem du eine Kopie davon in einem Container speicherst. Automatisch angelegte Objekte (also welche die nicht mit new angelegt wurden) werden bei Verlassen des jeweiligen Blocks zerstört, ohne wenn und aber. Egal ob der Block regulär oder per Exception verlassen wird.

    Oder andersherum - sollte man bei der Klassendefinition immer einen leeren Konstruktor bereitstellen, der ein "Minimalobjekt" liefert und innerhalb des Blocks überschrieben wird?

    Nein, sollte man nicht immer machen. Es gibt Objekte die ohne eine ordentliche Konstruktion keinen Sinn machen. Wenn die Konsturktion fehlschlägt, bleibt nichts als eine Exception zu werfen. Wenn die Exception fliegt, dann bekommt der aufrufende Code auch sein Objekt nicht, kann vermutlich nicht weiterarbeiten ohne das Objekt und dann machts auch nichts wenn die Exception die Bearbeitung abbricht - bis zu einem Punkt wo die Nichtbearbeitung verschmerzt werden kann, die Exception gefangen wird und weitergearbeitet wird.

    aber bei eigenen Klassen kommt da immer ein Fehler:

    eigeneKlasse instanz; //no matching function for call to `eigeneKlasse::eigeneKlasse()
    

    wie kann man denn obiges Verhalten erreichen?

    Das liegt daran dass bei Klassen, wo du einen eigenen Konstruktor definierst, der default-Konstruktor nicht mehr automatisch generiert wird. Bei der Definition der Variablen "instanz" wird immer der Konstruktor aufgerufen entsprechend den übergebenen Parametern. bei eigeneKlasse instanz; ist das kein Parameter, also der Default-Ctor.



  • vielen dank,

    Außer über Zeiger nur, indem du eine Kopie davon in einem Container speicherst.

    so ein Container wäre z.B. ein leeres Obejt angelegt über einen eventuell vorhandenen leeren Konstruktor oder? z.B.:

    inline QBitArray() {}
    

    Man als Container auch ein willkürliches Objekt anlegen, oder? Um eine Kopie in einem Container zu speichern muss man für die jeweilige Klasse aber auch den Zuweisungsoperator definieren,oder?

    Hab ich das richtig verstanden: um ohne Zeiger auszukommen muss man einen willkürlichen Container "außerhalb" anlegen oder den eventuell vorhandenen leeren Konstruktor benutzen. Um diesem Container dann die lokale Instanz zuzuweisen muss man den Zuweisungsoperator definieren.

    Eines ist mir bei QBitArray noch aufgefallen, man kann sowohl:

    QBitArray instanz;
    

    als auch:

    QBitArray instanz();
    

    schreiben. Dies scheint zumindest für Eclipse einen Unterschied zu machen -Was ist der Unterschied? Wird in beiden Fällen der Konstruktor ohne Parameter ausgewählt?



  • dermoritz schrieb:

    vielen dank,

    Außer über Zeiger nur, indem du eine Kopie davon in einem Container speicherst.

    so ein Container wäre z.B. ein leeres Obejt angelegt über einen eventuell vorhandenen leeren Konstruktor oder? z.B.:

    inline QBitArray() {}
    

    Man als Container auch ein willkürliches Objekt anlegen, oder? Um eine Kopie in einem Container zu speichern muss man für die jeweilige Klasse aber auch den Zuweisungsoperator definieren,oder?

    Nein, völlig falsch. Schau im Buch/Tutorial deiner Wahl mal das Kapitel zu Containern an (vector, list, set, map). Das wäre zu viel um es hier jetzt zu erklären 🙂 Vielleicht hast du dich auch nur unglücklich ausgedrückt. Container sind auf jeden Fall keine willkürlichen Objekte.
    Für die Standardcontainer muss der enthaltene Datentyp copy-konstruierbar sein und außerdem einen op= haben.

    Eines ist mir bei QBitArray noch aufgefallen, man kann sowohl:

    QBitArray instanz;
    

    als auch:

    QBitArray instanz();
    

    schreiben. Dies scheint zumindest für Eclipse einen Unterschied zu machen -Was ist der Unterschied? Wird in beiden Fällen der Konstruktor ohne Parameter ausgewählt?

    Das zweite ist die Deklaration einer Funktion mit Namen instanz, ohne Parameter, mit Rückgabewert QBitArray. Also was völlig anderes als die Konstrkution eines Objekts mit dem Default-Ctor.



  • ok dann gibt es noch ein dritte Möglichkeit neben Zeigern und Containern eine Variable nach außen zu holen, nämlich die mit dem leeren Konstruktor.

    meineKlasse instanz; //leerer Konstruktor definiert
    try{       //oder ander Block
     instanz=meineKlasse(blubber) //dafür muss auch der '='-Operator entsprechend definiert sein oder?
    }
    
    instanz.irgendwas
    

    Meine Frage ist wann und unter welchen Umständen macht es Sinn so einen Konstruktor bereit zu stellen (analog zu QBitArray.h:)

    inline QBitArray() {}
    

    Damit bietet die Klasse doch so eine Art Container für sich selbst, oder?! Die Problematik die zu diesem Thread geführt hat, lag in meiner eigenen Klasse begründet. In vielen anderen Fällen hab ich ganz Java-like erstmal ein leeres Objekt angelegt und im daraufolgenden Block ein "richtiges" Objekt zugewiesen. Erst jetzt ist mir bewusst geworden das sowas nur mit einem parameterfreien Konstruktor funktioniert und einem Zuweisungsoperator.
    Wann sollte ich denn solche Dinge für eigene Klassen vorsehen?

    um das mal zusammenzufassen (oder falls ich die ganze Zeit flashc lag): Was muss man machen damit das:

    QBitArray instanz;
    if(ok){
      instanz=QBitArray(bitLength,false);
    }
    

    mit eigenen Klassen funktioniert. Und macht es überhaupt Sinn?!



  • dermoritz schrieb:

    ok dann gibt es noch ein dritte Möglichkeit neben Zeigern und Containern eine Variable nach außen zu holen, nämlich die mit dem leeren Konstruktor.

    meineKlasse instanz; //leerer Konstruktor definiert
    try{       //oder ander Block
     instanz=meineKlasse(blubber) //dafür muss auch der '='-Operator entsprechend definiert sein oder?
    }
    
    instanz.irgendwas
    

    Nein. Mit dem Default-Konstruktor bleibt deine Klasse in genau dem Scope wo der Konstruktor aufgerufen wurde. Was du oben gemacht hast ist folgendes:
    - Du konsturierst das Objekt im äußeren Scope (Default-Ctor)
    - Du konstruierst ein zweites Objekt im try-Block und weist es dem ersten Objekt zu. (Und ja, der op= muss definiert sein dafür)
    - Das Objekt im try-Block wird nach der Zuweisung wieder zerstört.
    - nach dem try-Block rufst du eine Methode des Objektes auf, immernoch im äußeren Scope.
    Weder das Objekt instanz noch das temporäre Objekt ist dadurch außerhalb des Scopes erreichbar gewesen, in dem es erzeugt wurde.

    Damit bietet die Klasse doch so eine Art Container für sich selbst, oder?! Die Problematik die zu diesem Thread geführt hat, lag in meiner eigenen Klasse begründet. In vielen anderen Fällen hab ich ganz Java-like erstmal ein leeres Objekt angelegt und im daraufolgenden Block ein "richtiges" Objekt zugewiesen.

    Du musst vom Java-Denken loskommen. In Java legst du nicht die Objekte selber an, sondern nur Referenzen darauf. Dann erzeugst du Objekte mit new und lässt die Referenzen darauf zeigen. In C++ legst du die Objekte direkt an, statt nur Referenzen darauf. Das ist ein Riesenunterschied, das musst du verinnerlichen.



  • Was für mich aber entscheidend ist: "instanz" ist außerhalb des Blocks erreichbar. Und nach dem Block ist der Wert aus dem Block drinne - falls der Block durchlaufen wurde. Das Objekt was innerhalb nur für die Zuweisung erschaffen wurde ist danach kaputt - aber das ist ja gneau so gewollt.

    und viele Qt Klassen (und auch andere??) definieren dafür einen Konstruktor per:

    inline QBitArray() {}
    

    nennt man das immernoch Standarkonstruktor??

    Meien Hauptfrage bleibt aber bestehen: Unter welchen Umständen sollte man einen Konstruktor der obigen Art bereitstellen und zusätzlich einen Zuweisungsoperator oder anders herum: wäre es Mißbrauch diese 2 Dinge nur für diesen Zweck* zur Verfügung zu stellen?

    *um außerhalb von Blöcken "leere "Container" Objekte" erstellen zu können und sie innerhalb mit "sehr temporären" Objekten überschreiben zu können.



  • dermoritz schrieb:

    nennt man das immernoch Standarkonstruktor??

    Ja, oder Default-Konstruktor.

    Meien Hauptfrage bleibt aber bestehen: Unter welchen Umständen sollte man einen Konstruktor der obigen Art bereitstellen und zusätzlich einen Zuweisungsoperator oder anders herum: wäre es Mißbrauch diese 2 Dinge nur für diesen Zweck* zur Verfügung zu stellen?

    *um außerhalb von Blöcken "leere "Container" Objekte" erstellen zu können und sie innerhalb mit "sehr temporären" Objekten überschreiben zu können.

    Ja. Um die Dinge nur für den Zweck zur Verfügung zu stellen ist das Missbrauch. Und mit Containern hat das immernoch überhauptnichts zu tun. Nenns meinetwegen "uninitialisiert" auch wenns das nicht ganz trifft. Und Objekte sind entweder temporär oder nicht. "Sehr temporär" gibts nicht.

    Was du damit tust ist ja folgendes:
    - Du erschaffst ein Objekt in einem sinnfreien Zustand (wenn der Zustannd sinnvoll wäre hättest du den Default-Ctor sowieso und nicht nur für diesen "Hack")
    - Danach belegst du das Objekt vielleicht mit einem sinnvolleren Zustand.
    - Danach arbeitest du mit einem Objekt in einem eventuell sinnfreien Zustand weiter.
    Und das ist böse.

    Wenn du die Funktion nach dem try-Block benutzen willst, um dein vielleicht immernoch sinnfreies Objekt in einen sinnvollen Zustand zu bringen, dann tust du das Ganze am falschen Ort. Wenns diese Möglichkeit gibt, gehört das alles als Fehlerbehandlung in den Konstruktor selber und der Default-Ctor ist unnötig.



  • dermoritz schrieb:

    Was für mich aber entscheidend ist: "instanz" ist außerhalb des Blocks erreichbar. Und nach dem Block ist der Wert aus dem Block drinne - falls der Block durchlaufen wurde. Das Objekt was innerhalb nur für die Zuweisung erschaffen wurde ist danach kaputt - aber das ist ja gneau so gewollt.

    Wenn der try-Block nicht erfolgreich war, ist Dein Objekt nicht konstruiert worden. Welche Methoden willst Du anwenden, wenn das Objekt nicht existiert? Nochmal: Die Konstruktion ist fehlgeschlagen.

    Deshalb wäre die übliche Vorgehensweise immernoch, nach dem catch einfach nicht mehr auf das Objekt zuzugreifen. Auch nicht über "Hacks". Eigentlich sollte die Methode, wenn ihre Aufgabe ist, bestimmte Aktionen auf diesem Objekt auszuführen, bereits im catch beendet werden.

    nennt man das immernoch Standarkonstruktor??

    Ja.

    Meien Hauptfrage bleibt aber bestehen: Unter welchen Umständen sollte man einen Konstruktor der obigen Art bereitstellen und zusätzlich einen Zuweisungsoperator

    Wenn es Sinn macht, das Objekt mit keinem Parameter erzeugen zu können. Für ein QBitArray würde dieser Konstruktor ein Array mit 0 Elementen erzeugen, was durchaus ein gültiges Objekt ist.

    oder anders herum: wäre es Mißbrauch diese 2 Dinge nur für diesen Zweck* zur Verfügung zu stellen?

    Ja :p


Anmelden zum Antworten