Worin liegen die Unterschiede in diesen beiden Konstruktoren



  • Moin zusammen
    Dieses Mal ist's aber wirklich eine Konstruktor-Frage 😉

    Ich habe hier zwei Schreibweisen, wie man einen Konstruktor schreiben kann, der mit bestimmten Werten initialisiert werden soll. Nur verstehe ich nicht ganz, warum es dafür diese beiden Schreibweisen gibt, oder eher: Worin sie sich unterscheiden.

    Konstruktor 1:

    Kreis(int x, int y, double r) 
    {
       m_x = x;
       m_y = y;
       rad = r;
       if(rad<0) rad*=-1;
    }
    

    Konstruktor 2:

    Kreis(int x, int y, double r) : m_x(x), m_y(y), rad(r) 
    { 
      if(rad<0) rad*=-1;
    }
    

    Meines Wissens nach machen beide das gleiche: Sie weisen den Daten m_x, m_y und rad die Werte x,y,r zu. Aber wo genau liegen denn die Unterschiede oder Vorteile, dass man dafür zwei unterschiedliche Schreibweisen hat? (Beispiel 1 stammt aus einem Buch, Beispiel 2 aus dem Skript der Vorlesung)

    Würde mich sehr über Aufklärung freuen 🙂


  • Mod

    Der erste erzeugt erst die ints und weist ihnen dann Werte zu, der zweite erzeugt gleich ints mit den richtigen Werten.

    Die zweite Schreibweise ist zu bevorzugen, denn die erste hat keine Vorteile. Sie ist bestenfalls genausogut. Dafür gehen mit ihr ein paar Sachen gar nicht, die man häufig braucht, z.B. Initialisierung von Membern ohne Defaultkonstruktor.

    Und bei sehr komplexen Konstruktoren/Zuweisungsoperatoren ist nicht unbedingt klar, ob der Compiler die unnötigen Schritte in der ersten Variante wegoptimieren kann. Das wird sogar im allgemeinen sehr schwierig sein, daher würde ich sowieso nicht davon ausgehen, dass er dies tut. Bei int wird er's noch hinbekommen, aber Klassenmember deren Konstruktor evtl. gerade nicht als Code vorliegen werden praktisch unmöglich.



  • Dankeschön, das ist jetzt klarer geworden 🙂
    Ich werde mir dann mal die zweite Variante angewöhnen.
    Ist das eigentlich Zufall, dass die Schreibweise aussieht, als wäre es eine Vererbung? Also frei nach dem Motto: "Der Konstruktor Kreis soll von folgenden Werten erben : x,y,rad"?


  • Mod

    Das musst du Herrn Stroustrup fragen. Denk dran, dass nur begrenzt viele Sonderzeichen auf der amerikanischen Tastatur leicht zugänglich sind. Daher sind so viele Zeichen in C++ bereits mehrfach in ihrer Bedeutung überladen. Irgendetwas muss man ja nehmen und es muss ein Zeichen sein, welches da auf keinen Fall in einem anderen Zusammenhang auftauchen kann.



  • Schade, hätte ja klappen können 😉
    Aber ok, das nehme ich dann einfach mal so hin 🙂



  • Kreis(int x, int y, double r) : m_x(x), m_y(y), rad(r)
    

    Sowas nennt man dann Elementinitialisierer.
    Wie SeppJ schon sagte, muss man unter gegebenen Umständen auf diese Weise initialisieren, etwa wenn in der Klasse/Struktur Konstanten (const) auftauchen.

    Im Falle von Vererbung, muss eine abgeleitete Klasse den Konstruktor der Basisklasse sogar mit in dieser Liste aufführen. Man spricht dann von Konstruktor als Elementinitialisierer oder kurz: Basisinitialisierer.

    Grüße

    Schlitzauge 🙂



  • Wow, Schlitzauge, du hast mir gerade eine Frage beantwortet, die sich gerade eben erst beim Grübeln über ein Stückchen Code ergab 😃 (Also der Teil mit den abgeleiteten Klassen ^^)



  • Nayamis schrieb:

    Moin zusammen
    Ich habe hier zwei Schreibweisen, wie man einen Konstruktor schreiben kann, der mit bestimmten Werten initialisiert werden soll. Nur verstehe ich nicht ganz, warum es dafür diese beiden Schreibweisen gibt, oder eher: Worin sie sich unterscheiden.
    Würde mich sehr über Aufklärung freuen 🙂

    Soweit ich das mal gelesen habe, kann man Variablen auf unterschiedliche Weise
    mit Werten versehen.
    Z.B.

    int x=10;
    

    oder

    int x(10);
    

    Im ersten Fall erfolgt eine Initialisierung mittels Zuweisung, d.h. der aktuelle Wert wird gelöscht und durch den neuen ersetzt.
    Im zweiten Fall erfolgt die Initialisierung direkt.

    Das ist etwas effektiver, bei zwei Int-Werten dürfte das zwar nicht groß ins Gewicht fallen, bei größeren Klassen aber evtl. schon.



  • Das hat aber wenig mit den Unterschieden im Konstruktor zu tun. Zudem: http://ideone.com/APWk4


  • Mod

    redrew99 schrieb:

    Soweit ich das mal gelesen habe, ...

    Kaum. Und wenn doch, schmeiß das Buch/Tutorial etc. weg.
    Die Schreibweise ist bei Skalaren völlig irrelevant.
    Außer dass der inflationäre Klammergebrauch das Ganze unleserlich macht.



  • int x{42};
    


  • camper schrieb:

    redrew99 schrieb:

    Soweit ich das mal gelesen habe, ...

    Kaum. Die Schreibweise ist bei Skalaren völlig irrelevant.
    Außer dass der inflationäre Klammergebrauch das Ganze unleserlich macht.

    Ok, dann frage ich mich aber, warum man nicht

    Kreis(int x, int y, double r) : m_x=x, m_y=y, rad=r
    

    geschrieben hat.



  • redrew99 schrieb:

    camper schrieb:

    redrew99 schrieb:

    Soweit ich das mal gelesen habe, ...

    Kaum. Die Schreibweise ist bei Skalaren völlig irrelevant.
    Außer dass der inflationäre Klammergebrauch das Ganze unleserlich macht.

    Ok, dann frage ich mich aber, warum man nicht

    Kreis(int x, int y, double r) : m_x=x, m_y=y, rad=r
    

    geschrieben hat.

    Weil es lästig wäre wenn man jedes mal

    Foo(int x, int y, double r) : m_bar = Bar(x, y, r)
    

    schreiben müsste.



  • SeppJ schrieb:

    Der erste erzeugt erst die ints und weist ihnen dann Werte zu, der zweite erzeugt gleich ints mit den richtigen Werten.

    Hört bitte mal auf, dass mit ints zu erklären, da ist es nämlich falsch. Ints werden nicht so erstellt wie Klassen und deshalb kann man ihnen beim "erstellen" auch nicht werte zuweisen.



  • aaargg schrieb:

    SeppJ schrieb:

    Der erste erzeugt erst die ints und weist ihnen dann Werte zu, der zweite erzeugt gleich ints mit den richtigen Werten.

    Hört bitte mal auf, dass mit ints zu erklären, da ist es nämlich falsch. Ints werden nicht so erstellt wie Klassen und deshalb kann man ihnen beim "erstellen" auch nicht werte zuweisen.

    Mit "Ints" ist es genauso richtig wie mit Klassen oder sonstwas:
    Vor dem Eintritt in den Konstruktorkörper muss natürlich erst der Speicher für das Objekt, das gerade konstruiert werden soll, initialisiert werden. Dafür wird zuerst die Initialisierungsliste abgearbeitet. Member (oder die Basis), die nicht in der Initialisierungsliste initialisiert werden Default-initialisiert (mit entsprechend definiertem oder undefiniertem Wert, je nach Typ). Wenn diesen dann im Konstruktorkörper ein Wert zugewisen wird, wurden sie bereits initialisiert und bekommen nun (unnötigerweise) ein zweites Mal einen Wert verpasst.
    Das ist genau das selbe wie das hier:

    int x; // Deklaration und Definition mit Default-Initialisierung
    x=10; // Wertzuweisung
    

  • Mod

    arghonaut schrieb:

    Member (oder die Basis), die nicht in der Initialisierungsliste initialisiert werden Default-initialisiert (mit entsprechend definiertem oder undefiniertem Wert, je nach Typ).

    Du meinst sicher einen bestimmten oder unbestimmten (indeterminate) Wert. Soweit ist das schon richtig.
    Der entscheidende Punkt dabei ist aber, dass ein unbestimmter Wert eben gerade nur dann entsteht, wenn kein bestimmter Wert in einem Objekt gespeichert wird, mit anderen Worten: der Speicher wird überhaupt nicht angefasst.
    Dann ist

    arghonaut schrieb:

    Wenn diesen dann im Konstruktorkörper ein Wert zugewisen wird, wurden sie bereits initialisiert und bekommen nun (unnötigerweise) ein zweites Mal einen Wert verpasst.

    das irreführend, denn man kann nur bestimmte Werte einem Objekt "verpassen", die zweite "Initialisierung" (technisch eine Zuweisung) ist tatsächlich das erste Mal, dass ein bestimmter Wert in dem Objekt gespeichert wird.

    arghonaut schrieb:

    Das ist genau das selbe wie das hier:

    int x; // Deklaration und Definition mit Default-Initialisierung
    x=10; // Wertzuweisung
    

    Richtig, nur dass eben bei der Default-Initialisierung von Skalaren gar nichts passiert.

    C++11 schrieb:

    8.5 Initializers [dcl.init]
    [...]
    6 To default-initialize an object of type T means:
    — if T is a (possibly cv-qualified) class type (Clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);
    — if T is an array type, each element is default-initialized;
    — otherwise, no initialization is performed.
    If a program calls for the default initialization of an object of a const-qualified type T, T shall be a class type with a user-provided default constructor.



  • Erstmal sorry das ich erst jetzt meinen Senf hinzugebe nachdem soviel Verwirrung hier geschaffen wurde.
    Bevor du einen Konstuktor einer Klasse aufrufst muss der Konstruktor der Basisklasse aufgerufen. Da es Basisklassen mit mehreren Konstruktoren gibt ist es auch sinnvoll konkret zu sagen welcher Konstruktor der Basisklasse aufgerufen werden soll. Das ist der eigentliche Grund warum es die Initialisierungsliste gibt.
    Schöner Seiteneffekt der auch schon angesprochen wurde du kannst den const-Member einen Wert zuweisen [korrekter: zuordnen] anstatt es bei der definition zu tun. Das gleiche gilt für referenzen.
    Hier wurde gesagt das bei der InitListe einmal ein Wert den Membern zugewiesen wird bei der anderen Methode 2mal. Das ist falsch. Es wäre echt schön wenn den Sachen wie ints ein Defaultwert zugewiesen würde aber dem ist nicht so und man fragt sich manchmal bei Programmfehlern wie kommt da jetzt einmal eine 16 hin und ein andermal eine -37379148. Dies passiert nämlich dadurch das dort gerade der Wert genommen wird der da steht; da wird nix zugewiesen. Natürlich steht in fast allen Büchern die InitListe ist zu bevorzugen aus Perfomance gründen (die haben all von Scott Meyers abgeschrieben der urprünglich nicht von der c++-Schiene kommt und manch schlechte angewohnheiten hat. Trotzdem Scott Meyers erklärt gut und man kann alles in der Praxis gebrauchen). Auch Copy-Konstruktoren(die in der InitListe aufgerufen werden) können schlecht programmiert werden und zu Performance-Schwächen führen. Wenn dein Programm performance schwach ist dann liegt dies nicht daran dass du die eine oder andere Methode verwendest. Für Performance-kritische Programme wird sowieso c oder gleich Assembler(teilweise an kritischen Stellen) benutzt.
    Schlusswort: Welche Methode sollst du benutzen? Die die dein Team zum Zuweisen der Werte für die Member auch benutzt. Faustregel bei uns in der Firma: alles was keine einfache Typen wie int, double, float, char sind kommen in die InitListe.
    Zu einem möcht ich noch mein Senf hinzugeben. Es gibt Compiler die die Initliste nach der Klassendefinition abarbeiten. Aber ich würde mich nicht darauf verlassen.
    Beispiel:
    class A
    {
    A(int);
    int & b;
    const int c;
    }

    A::A(int x) : c(x), b(c)
    {}

    Dies werden wohl nicht alle Compiler richtig machen da zuerst b und dann c initialisiert wird. Ansonsten noch viel spass bei allen Compilern die du in Zukunft haben wirst 😉


  • Mod

    Erst b dann c ist aber richtig! Das heißt, der Code den du da hast, ist falsch, weil er b mit etwas uninitilaisiertem initialisiert. Wenn ein Compiler das andersrum macht, dann liegt dieser falsch! Welcher Compiler macht das?



  • Ich bin mir nicht sicher aber während einem Tutorial von mir ist ein Schüler aufgesprungen und hat gesagt bei ihm würde er es nach der InitListe machen. Deswegen schrieb ich dass man sich nicht darauf verlassen sollte dass das jeder so macht. Ich glaube es war ein Borland-Compiler aber beschwören könnt ich es nicht. Wenn ich es in erfahrung bringen kann schreib ich es in einem Beitrag 🙂


Anmelden zum Antworten