Anfängerfragen



  • Und was genau ist das Fehlverhalten?

    Meine Fresse, dir muss man echt jeden Schei** aus der Nase ziehen 😞


  • Mod

    Da sind sowohl die 5 leeren Unsinnsinstanzen drin, von denen du gesagt hast, dass du sie weg gemacht hast, als auch, wie von daddy_felix vermutet, gleich mehrere Verletzungen der Regel der großen Drei. Das heißt, trotz mangelnder Fragestellung (siehe Beitrag von otze) haben die Helfer sogar richtig geraten. Trotz ihrer Antworten sind aber genau diese Fehler noch da. Die Regel der großen Drei kann ich ja verstehen, das ist für Anfänger nicht einfach*. Aber die 5 Instanzen im vector nach mehrmaligem Hinweis?

    Dein Klassendesign ist sehr merkwürdig. Konstruktor und Destruktor einer Klasse sind nicht vergleichbar mit dem Konzept einer Kontoeröffnung oder -schließung. Der Rest der Klassengestaltung ist auch nicht so toll, aber da andere Baustellen gerade dringender sind (immer noch die 5 Instanzen!) lasse ich mich dazu mal nicht groß aus.

    *: Und ausgerechnet Anfänger scheinen magisch das Bedürfnis zu verspüren, ihre Klassen so zu gestalten, dass sie die Regel der großen Drei benötigen. Ich kann mich kaum erinnern, wann ich das letzte Mal wirklich produktiv einen selbstdefinierten Destruktor geschrieben habe.



  • Oh man sry sry. Das Problem ist:

    1. dass statt der Menueausgabe direkt eine Instanz erzeugt wird.
    2. dass die Instanz direkt gelöscht wird.

    Zu Sepp die war 5 da weil ich nochmal geguckt habe ob sich das Vehalten nicht doch irgendwie anders bemerkmbar ist. Also ob ich was übersehen habe. Was leider nicht der Fall ist.


  • Mod

    kamelkacke schrieb:

    1. dass statt der Menueausgabe direkt eine Instanz erzeugt wird.
    2. dass die Instanz direkt gelöscht wird.

    Die Antworten da drauf sind immer noch die gleichen, die schon mehrmals gegeben wurden. Ob mit oder ohne 5, so ist immer noch die Korrespondenz Kontoeröffnung/schließung <-> Konstruktor/Destruktor nicht zum Klassenkonzept von C++ passend (mit etwas Drücken kann man es vielleicht da rein quetschen, aber es wird nicht gut) und selbst wenn, dann wurde es technisch falsch umgesetzt (Regel der großen Drei).



  • Nagut dann lass ixh mi das nochmal alles auf der Zunge sergehen. Vielen Dank an alle für eure Geduld und Einsatz!!



  • Erzeugt dieser code Speicherleaks oder gibts sonst irgendwas daran auszusetzen? Ausser die großen drei.

    #include <iostream>
    #include <string>
    #include <list>
    
    using namespace std;
    
    string a = "ä";
    string o = "ö";
    string u = "ü";
    
    class musssein {
    
    	private:
    
    	int id;
    
    	public:
    
    	musssein (int idparam) {
    		cout << "Raumschiff wird erstellt!\n";
    		id = idparam;
    	}
    
    	~musssein () {
    		cout << "Raumschiff wird gel" << o << "scht\n";
    	}
    
    	void starten () {
    		cout << "Raumschiff " << name << " mit der ID " << id << " startet\n";
    	}		
    };
    
    int main () {
    
    	int anzahl = 0;
    
    	list <musssein*> lmusssein;
    	list <musssein*> :: iterator i; 
    
    	musssein *temp;
    
    	cout << "Anzahl der zu erstellenden Schiffe eingeben\n";
    	cin >> anzahl;
    
    	for (int j; j<anzahl; j++) {
    				temp = new musssein (j+1);
    				lmusssein.push_back (temp);
    	}	
    	for (i=lmusssein.begin (); i!=lmusssein.end (); i++) {
    		(*i)->starten ();
    	}	
    	for (i=lmusssein.begin (); i!=lmusssein.end (); i++) {
    		delete (*i);
    		(*i) = NULL;
    	}
    }
    

  • Mod

    Abgesehen davon, dass der Code nicht compiliert (Zeile 30):

    Geht sofort los mit globalen Variablen (natürlich total sinnlose Funktion dieser Variablen), danach eine Klasse mit einem Unsinnsnamen, dann kommen ohne Grund Zeiger in die Liste (vermutlich, weil du wohl immer noch nicht verstanden hast, wieso deine Instanzzähler nicht funktionieren. Teufel mit Belzebub ausgetrieben), eine komische Wahl des Containers, unpassender Scope der zudem unnötigen Variable "temp" (der Name allein reicht schon, um zu wissen, dass sie unnötig ist), uninitialisierte Variablen, und als krönender Abschluss hast du wohl irgendwo Code abgeschrieben, in dem Zeiger auf NULL gesetzt werden, ohne zu verstehen, wieso dies getan wurde (damit bist du aber immerhin in guter Gesellschaft, dies ist wohl einer der häufigsten Fehler bei der Benutzung von Zeigern).
    Das das ganze Speicher leckt im Fall einer Exception fällt da kaum mehr auf.

    Ein paar oberflächliche Korrekturen der schlimmsten Fehler:

    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Raumschiff 
    {
    private:
      int id;
    
    public:
      explicit Raumschiff (int idparam) : id(idparam)
      {
        cout << "Raumschiff wird erstellt, id = " << id << '\n';
      }
    
      ~Raumschiff () 
      {
        cout << "Raumschiff wird gelöscht, id = " << id << '\n';
      }
    
      Raumschiff (const Raumschiff & other) : id(other.id)
      {
        cout << "Raumschiff wird kopiert, id = " << id << '\n';
      }
    
      void starten () {
        cout << "Raumschiff mit der ID " << id << " startet\n";
      }      
    };
    
    int main ()
    {
      vector <Raumschiff> raumschiffe;
    
      cout << "Anzahl der zu erstellenden Schiffe eingeben\n";
      int anzahl;
      cin >> anzahl;
    
      for (int i = 0; i<anzahl; i++) 
      {
        raumschiffe.push_back(Raumschiff(i+1));
      }  
      for (vector<Raumschiff>::iterator i=raumschiffe.begin(); i != raumschiffe.end (); ++i)
      {
        i->starten ();
      }  
    }
    

    http://ideone.com/noFFkw
    Vielleicht geht dir beim Ausführen auch endlich ein Licht auf, wieso deine Instanzenzähler nie funktionierten.

    Ich habe auch noch SEHR viele Kleinigkeiten korrigiert, die ich nicht weiter erwähnt habe, vergleich am besten mal Zeile für Zeile, was anders ist und überleg, warum ich das wohl so gemacht habe. Achte auch auf vermeintliche Kleinigkeiten.



  • Gold. Hab mal das Kapitel Kopierkonstruktor meines Buches mal gelesen und unter "In ferner Zukunft brauch ich das mal.." abgehackt. Keine Ahnung warum. Ansonsten danke für die perfektionierung. Werde es mir gleich verinnerlichen. Danke!



  • Hey, drei Fragen zu Seppjs letzten Beitrag, oberhalb seines geposteten posts:

    1. Was spricht dagegen, einen zeiger mit NULL zu initiieren? Hab gelesen, dass man das macht, um unvorhergesehene ereignisse zu unterbinden (undefined behavior glaub ich).

    2. Wenn keine exception vorliegt, wird bei new, delete kein speicher geleaked, richtig?

    Unabhängig von Seppjs post:

    3. Was habt ihr alle gegen new und delete?

    Edit:

    4. Falls trotzdem eine exception auftreten sollte, koennte man das doch mit try&catch oder assert abfangen oder dient das nur zur Fehlersuche?

    5. Was ist eine superklasse? Eine klasse von der geerbt wird?



  • new & delete müssen als solches balanciert für jeden Speicherblock auftreten, ansonsten gibt es einen Leak. Um die Balancierung zu gewähren, benötigt es die entsprechende Aufmerksamkeit und es können einem leicht Fehler in den abstrusesten Randfällen unterlaufen.
    Das alles vor dem Hintergrund, dass die Benutzung selten Not tut, weil es für die überwiegenden Fälle robuste, fertige Klassen gibt, die einem zusammen mit den Lebenszeit-Regeln von C++ Sicherheit "garantieren".
    Aber auch wenn man der perfekte new&delete-Roboter ist, so würde der Verwaltungscode und die Fehlerbehandlung den eigentlich interessanten Teil des Codes inaktzeptabel verrauschen, wenn man den nicht auslagert und somit das Rad neu erfindet.



  • @seppj

    Kurze Zwischenfrage: Warum hast du den Konstructor explicit gemacht?



  • Ruvi++ schrieb:

    Kurze Zwischenfrage: Warum hast du den Konstructor explicit gemacht?

    um implizite Konversionen von int nach Raumschiff zu verhindern.



  • daddy_felix schrieb:

    Ruvi++ schrieb:

    Kurze Zwischenfrage: Warum hast du den Konstructor explicit gemacht?

    um implizite Konversionen von int nach Raumschiff zu verhindern.

    Entschuldige, irgendwie steh ich auf dem Schlauch.

    Wie kann das denn in dem geschriebenen Code passieren?
    Ich uebergebe doch einfach einen int Parameter als Argument an den Konstruktor der damit eine interne Variable initialisiert?



  • Damit verhindert SeppJ sowas:

    std::vector<Raumschiff> v;
    v.push_back( 1 );
    

    Taucht im Code so zwar nicht auf, aber wer weiß, was TE noch so alles vorhat.


  • Mod

    Ruvi++ schrieb:

    Wie kann das denn in dem geschriebenen Code passieren?

    Ganz einfach:

    void foo( Raumschiff );
    

    Nun ist es möglich, foo mit 4 als Argument aufzurufen. Das ist aber definitiv nicht wünschenswert, da wir ja wollen, dass im Code explizit steht, dass das Argument ein Raumschiff initialisiert.

    Edit: Zu spät.


  • Mod

    Ein paar Beispiele:

    struct Foo
    {
      explicit Foo(int){}
    };
    
    struct Bar
    {
      Bar(int){}
    };
    
    void foobar(Foo, Bar);
    
    int main()
    {
      Foo f1, 
        f2 = 5,
        f3(6),
        f4 = static_cast<Foo>(7),
        f5 = (Foo) 8;
    
      Bar b1,
        b2 = 5,
        b3(6),
        b4 = static_cast<Bar>(7),
        b5 = (Bar) 8;
    
      foobar(f1,b1);
      foobar(1,2);
      foobar(f1,2);
      foobar(1,b1);
    }
    

    Welche gehen, welche nicht? Sind die wohl alle so gewünscht, wenn Foo/Bar ein Raumschiff modellieren?

    Ein etwas extremeres, aber praxisnäheres, Beispiel ist std::vector:

    explicit vector (size_type n);
    

    Wenn das nicht so wäre, könnte man:

    vector<string> foo = 3;
    

    Sicherlich nicht gewollt.

    edit: viel zu spät...



  • Erstmal vielen Dank!

    Aber ich bin ehrlich gesagt ein wenig verwirrt.
    Mir war ueberhaupt nicht bewusst, dass einige Sachen die ihr da geschrieben habt ueberhaupt funktionieren.

    Ich waere niemals auf die Idee gekommen sowas zu schreiben:
    Mir ist auch nicht verstaendlich warum, das ueberhaupt funktioniert:

    Bar b2 = 5;
    

    Ich bin bis jetzt auch noch nie auf die Idee gekommen sowas zu schreiben:

    Foo f4 = static_cast<Foo>(7); 
    Foo f5 = (Foo) 8;
    

    Bedeutet, dass das hier eure Konstruktoren so gut wie immer explizit macht?


  • Mod

    Ruvi++ schrieb:

    Bedeutet, dass das hier eure Konstruktoren so gut wie immer explizit macht?

    Das kommt drauf an. Wenn das, was als Parameter genommen wird, nicht direkt etwas mit der erzeugten Klasse zu tun hat, dann auf jeden Fall explicit. Dieser Fall liegt hier vor, denn man hat einen Integer und ein Raumschiff, der Integer soll aber nicht wie ein Raumschiff benutzt werden können.

    Wenn die Klasse aber etwas darstellt, das mit dem Parametertyp verwandt ist, dann will man nicht unbedingt ein explicit. Beispiel eine Bruchzahlklasse. Da will ich auch andere Zahlen einsetzen können, wo eine Bruchzahl erwartet wird, die dann ggf. umgewandelt werden soll. Daher einen Konstruktor, der einen arithmetischen Typ nimmt, hier nicht explicit.

    Den zweiten Fall hat man in der Praxis eher seltener als den ersten.



  • Ruvi++ schrieb:

    Ich waere niemals auf die Idee gekommen sowas zu schreiben:...

    Man denkt nicht unbedingt in jeden Moment logisch, vielleicht ist man einfach abgelenkt usw. Und glaub mir: das kann auch mal im Produktivcode passieren (wie bei uns kürzlich bei der Umstellung von char => wchar_t...).


  • Mod

    Ruvi++ schrieb:

    Foo f4 = static_cast<Foo>(7); 
    Foo f5 = (Foo) 8;
    

    Das war didaktisch etwas ungünstig gewählt, da ich schreibfaul war. Das ist keine typische Initialisierung. Besser wäre etwas in dieser Richtung:

    Foo f1, 
        f2 = 5,
        f3(6);
    
      Bar b1,
        b2 = 5,
        b3(6);
    
      foobar(f1,b1);
      foobar(1,2);
      foobar(f1,2);
      foobar(1,b1);
    
      foobar(static_cast<Foo>(7), static_cast<Bar>(7));
      foobar((Foo) 8, (Bar) 8);
    

    Ruvi++ schrieb:

    Mir ist auch nicht verstaendlich warum, das ueberhaupt funktioniert:

    Bar b2 = 5;
    

    Interessante Frage, die meisten Leute würden wohl eher fragen, wieso das bei Foo nicht funktioniert. Wieso geht das bei Bar? Nun, weil der Standard sagt, dass man Variablen so initialisieren darf. Das nennt man "direct intialization" und "copy initialization". Der subtile Unterschied ist: Bei der direkten Initialisierung wird direkt der passende Konstruktor aufgerufen, sofern er existiert. Bei der copy initialization muss hingegen das, was auf der rechten Seite steht, über implizite Umwandlungen in den passenden Typ umgewandelt werden. Das geht hier bei Bar, da es eine solche Umwandlung gibt, bei foo jedoch nicht. Außerdem muss der Kopierkonstruktor zur Verfügung stehen. Er wird zwar wegoptimiert werden, aber er muss verfügbar sein, damit die Aktion stattfinden kann (spielt hier aber keine Rolle).
    In keinem dieser Fälle wird jemals der Zuweisungsoperator angepackt, obwohl ein Gleichheitszeichen benutzt wird.

    P.S.: Wieso das die meisten Leute andersrum fragen würden? Weil sie wissen, dass folgendes keinen Unterschied macht:

    double d(5);
    double e = 5;
    

    Macht beides das gleiche und es ist dem double auch egal, dass die 5 ein int ist.


Anmelden zum Antworten