Argumentloser Konstruktor



  • Hi @wob,

    ich wollte nur korrigieren was ich mit der Aussage zu der Referenz meinte. (Offensichtlichtlich fehlt mir Erfahrung im Sprachgebrauch der Cpp community und ich habe Referenz falsch verwendet).

    Wie genau sähe dann eine sinnvolle Übergabe aus? Ist es dann dieser Ansatz:

    B::B(A a_):
    a(a_)
    {
    }
    

    von @Schlangenmensch?
    Wenn ja wäre es schön, wenn du mir erklären könntest was genau hier passiert bzw a(a_) bedeutet. (Oder heist das ich muss ein Konstruktor für A(A: a) schreiben?)

    Eine erzeugung von A in B kommt leider eigentlich nicht in Frage. A ist ein relativ großes Objekt mit dem ich ein bisschen Arbeite bevor ich es an B weitergebe (weshalb ich eine Kopie von A vermeiden möchte). B braucht dann einige Methoden von A für seine Arbeit.

    Viele Grüße
    NPC



  • Die logische Frage ist: soll das Objekt a in der Klasse B unabhängig von dem übergebenen Objekt a_ arbeiten? Wenn nicht, d.h. du hast ein anderes Objekt, das mindestens eine so lange Lebenszeit wie das erzeugte B-Objekt hat, dann kannst du diese als Referenz oder Zeiger übergeben (und auch als Member so speichern). Evtl. noch const setzen, falls du nur lesend auf die (ebenfalls const) Member zugreifen möchtest (Stichwort: const correctness).

    Und zu : a(a_) habe ich dir oben schon das Stichwort (sowie den Link) "Initialisierungsliste" gegeben - dies ist ein wichtiges Feature bei Erstellen von Klassenkonstruktoren!



  • @NPC Da tauchen ja noch mehr Fragen auf...

    Was heißt denn "großes Objekt"?

    In meinem Ansatz wird der Member a mit dem impliziet erstellten Copy Ctor erstellt. Tatsächlich wird der sogar 2xmal aufgerufen, das erste mal beim Aufruf des Ctors und das zweite mal bei der Erstellung des Members.

    Das kann man auf eine Kopie reduzieren, wenn man z.B. eine const Referenz nutzt B::B(const A& a_):a(a_){} (das ist, was ich eigentlich machen würde)

    Bei deinem Ansatz machst du aber auch zwei Kopien, einmal bei der Übergabe im Ctor und einmal bei der Zuweisung.

    Wenn das Objekt wirklich groß ist, dann kommt eventuell tatsächlich ein Pointer oder eine Referenz in Frage, oder eine universal Referenz und ein Move. Das kommt dann auf die genaue Ausgestaltung an und hat auch seine Fallstricke.

    Vielleicht ist auch dein Design mit den beiden Klassen eher schlecht gewählt, aber auch das kommt auf das konkrete Problem an.

    Tatsächlich glaube ich, solange du dir Gedanken um solche Probleme machen musst, sollte Performance erstmal nicht dein Hauptaugenmerk sein.



  • Hi @Th69,

    a soll nicht unabhängig von a_ arbeiten. D.h., wenn ich dich richtig verstehe, eine übergabe als Referenz oder Zeiger ist ok.

    Mit Initialisierungslisten werde ich mich dann jetzt wohl mal beschäftigen xD.

    Viele Grüße
    NPC



  • Hi @Schlangenmensch,

    d.h., dass das Objekt exponentiell mit einem Parameter wächst. Die konkrete Basis ist abhängig von einem anderen Parameter und spielt eigentlich auch keine Rolle. Das Objekt wird am Ende gut und gerne 20-30 Gb groß (Es liegt sehr nah an einem auf einem Blatt errechneten Mindestgröße die aus dem Problem selbst folgt). Dabei arbeitet A im wesentlichen als, schon komprimierter [wieder auf dem Blatt], Container.

    Leider muss ich mich, der Aufgabe wegen, hier auf die Performance fokusieren.
    Die Frage die sich sofort stellt ist natürlich die wie sehr ich auf Performance schauen kann, wenn mir dergleichen Eigenschaften unbekannt sind. Das ist (hoffentlich) weniger kritisch als es scheint. Mein Ursprüngliches Problem lag an einer misinterpretation der Übergabe selbst. Mir war nicht bewusst, dass ich nicht automatisch einen Pointer übergebe, wenn ich ein Objekt übergeben will. Da bin ich wohl aus anderen Programmiersprachen zu sehr verwöhnt.

    Viele Grüße
    NPC



  • @Th69 sagte in Argumentloser Konstruktor:

    Bei der Kopie von A wird der (implizite) Kopierkonstruktor A(const A&) aufgerufen und an den Konstruktor übergeben.
    Bei jedem Konstruktor, also auch bei
    B::B(A a_) {
    a = a_;
    }
    werden zuerst die Member default-initialisiert (bevor der Methodenblock ausgeführt wird),

    Bin mir nicht sicher, ob das nicht das ist, was ich sagte, wenn nicht gar widersprüchlich...

    @NPC Nimm B(A & a_) und fertig. In den meisten Fällen möchte man das haben. Der Aufrufer muss sich dann aber darum kümmern.



  • @NoIDE sagte in Argumentloser Konstruktor:

    @NPC Nimm B(A & a_) und fertig. In den meisten Fällen möchte man das haben

    Naja, "meistens" will man ...const A&... mit const haben. Man muss aber in C++ schon immer wissen, was man tut, gerade was Ownership und Parameterübergabe angeht. Und man kann schlecht ohne genaue Kenntnis des Problems sagen "nimm A" oder "nimm B". Vielleicht will man eine Kopie machen, vielleicht sogar einen shared_ptr nehmen, wer weiß.



  • @NoIDE Und fertig? Zeig mal, wie die kurze Klasse dann bei dir aussehen würde?



  • @NoIDE sagte in Argumentloser Konstruktor:

    Bin mir nicht sicher, ob das nicht das ist, was ich sagte, wenn nicht gar widersprüchlich...

    Es wird aber keine Instanz von A kopiert, sondern der Member wird initialisiert.



  • Wenn ich da jetzt etwas hin klatsche, von dem ich glaube, dass es richtig ist, da ich die genauen Anforderungen ja nicht kenne, dann ist ihm ja nicht weitergeholfen.

    Ich denke, einige vergessen hier gerne, dass auch sie mal klein angefangen haben.



  • @NoIDE Nein, es geht darum, dass deine Empfehlung nicht einfach so funktioniert. Der TE hat ja ein Beispiel gepostet.

    B::B(A& a_):
    {
     a = a_;
    }
    

    geht nämlich nicht. Wenn der Member a eine Referenz ist, kann der nämlich nicht uninitialisiert sein.



  • @Schlangenmensch
    Jo, das war mir bewusst. Wenn sich der Parameter zu Referenz ändert, dann muss das natürlich auch konsequent durchgezogen werden...



  • @NoIDE Ich glaube, du weißt nicht wo von ich Rede. "Verwende Referenz und dann geht das mit der Zuweisung im Ctor" ist schlicht falsch.



  • @Schlangenmensch sagte in Argumentloser Konstruktor:

    "Verwende Referenz und dann geht das mit der Zuweisung im Ctor"

    Den zweiten Teil dieses Gliedsatzes Parataxes hab ich aber so nicht geschrieben. 😉



  • @NoIDE sagte in Argumentloser Konstruktor:

    @Schlangenmensch sagte in Argumentloser Konstruktor:

    "Verwende Referenz und dann geht das mit der Zuweisung im Ctor"

    Den zweiten Teil dieses Gliedsatzes Parataxes hab ich aber so nicht geschrieben. 😉

    Deswegen die Aufforderung, dass du konkret den Code schickst, den du hier für dieses konkrete Problem vorschlägst. Wenn du einfach nur sagst, macht XYZ (und sonst nix), dannm hast du eine Referenz mit Zuweisung im Ctor. Insofern doch, der zweite Teil war impliziert in deiner Aussage.

    @NoIDE sagte in Argumentloser Konstruktor:

    @Th69 sagte in Argumentloser Konstruktor:

    Bei der Kopie von A wird der (implizite) Kopierkonstruktor A(const A&) aufgerufen und an den Konstruktor übergeben.
    Bei jedem Konstruktor, also auch bei
    B::B(A a_) {
    a = a_;
    }
    werden zuerst die Member default-initialisiert (bevor der Methodenblock ausgeführt wird),

    Bin mir nicht sicher, ob das nicht das ist, was ich sagte, wenn nicht gar widersprüchlich...

    @NPC Nimm B(A & a_) und fertig. In den meisten Fällen möchte man das haben. Der Aufrufer muss sich dann aber darum kümmern.

    Nein, es ist nicht das, was du sagtest. Du hast gesagt:

    @NoIDE sagte in Argumentloser Konstruktor:

    das heißt, jedes Mal, wenn eine Instanz von B erstellt wird, wird versucht, die Instanz von A zu kopieren, was aber scheitert, da A keinen parameterlosen Konstruktor hat.

    Es stimmt, dass die Instanz von A kopiert wird (erster Teilsatz), aber es stimmt nicht, dass es daran scheitert (zweiter Teilsatz). Es scheitert daran, dass keine Initalisierungsliste verwendet wird. Im Gegensatz zu Java finden Member Initialisierungen dort statt. Hat man die nicht, wird das Objekt default initialisiert und dann fehlt der parameterlose Konstruktor.

    Also nochmal zusammengefasst:

    • Es liegt NICHT an der Übergabe per Value, es würde genauso auch bei einer Referenz scheitern
    • Einen parameterlosen Konstruktur anzulegen "löst" das Problem insofern, dass dann die Fehlermeldung weg geht
    • Die eigentliche Lösung ist aber eine Intialisierungsliste zu verwenden
    • Übergabe an den Konstruktur per Kopie, Pointer, Referenz oder const Referenz hängt dann vom use case ab (d.h. Ownership Requirements, Semantik, Größe der Objekte etc.). Vor allem beim Konstruktor gibt es nicht die eine Art, die immer sinnvoll ist.


  • @Leon0402
    💯

    Ich hab aber noch eine Frage, das von dir beschriebene, "merkwürdige" Verhalten gilt aber nur bei Ctor-Aufrufen, oder?



  • @NoIDE sagte in Argumentloser Konstruktor:

    @Leon0402
    💯

    Ich hab aber noch eine Frage, das von dir beschriebene, "merkwürdige" Verhalten gilt aber nur bei Ctor-Aufrufen, oder?

    Ich weiß nicht, was du genau mit merkwürdigem Verhalten meinst? Das Problem hängt wie von mir beschrieben eben mit der Konstruktion des Objektes zusammen, daher tritt es also natürlich auch nur bei der Konstruktion von Objekten auf. Dort aber immer. Egal ob man jetzt den CTor explizit definiert oder nicht. Ich weiß aber nicht, was da genau merkwürdig sein soll.

    Es ist vlt. merkwürdig aus Sicht eines Java Programmierers in folgender Hinsicht:

    • In Java werden meines Wissens nach Felder (Members) erst initialisiert, nachdem der Body des Konstruktors ausgeführt wird. Eine Initialisierungsliste gibt es nicht. Bei C++ eben vor dem Body, dafür gibt es aber halt die Initialisierungsliste
    • In Java hat man Referenztypen für Objekte (neben primitiven Datentypen) und deren Default Value ist null analog zu Pointern in C++ (nullptr). Bei der Default Initialisierung wird also nie ein Objekt konstruiert und entsprechend gibt es solch einen Fehler wie in C++ auch nicht in Java

    In beiden Fällen ist es aber nicht merkwürdig, sondern eben unterschiedliches Sprachdesign und somit höchstens für den entsprechenden Programmierer unüblich, der es anders gewohnt ist.



  • @Leon0402 sagte in Argumentloser Konstruktor:

    In Java werden meines Wissens nach Felder (Members) erst initialisiert, nachdem der Body des Konstruktors ausgeführt wird. Eine Initialisierungsliste gibt es nicht. Bei C++ eben vor dem Body, dafür gibt es aber halt die Initialisierungsliste

    Hm, idR. beim Aufruf des ctors, oder sie sind static, dann bereits davor. Konstante (final) Felder müssen übrigens immer einen Wert haben, also entweder direkt bei der Deklaration oder sie werden im Ctor initialisiert.

    @Leon0402 sagte in Argumentloser Konstruktor:

    In Java hat man Referenztypen für Objekte (neben primitiven Datentypen) und deren Default Value ist null analog zu Pointern in C++ (nullptr). Bei der Default Initialisierung wird also nie ein Objekt konstruiert und entsprechend gibt es solch einen Fehler wie in C++ auch nicht in Java

    Es gibt die Default-Initialisierung, ja. Aber im Unterschied zu C++ werden dabei keine neuen Instanzen erstellt (wenn man das nicht explizit angibt).

    In Java gibt es keine Variablen (auch keine Member/Felder), die keinen Wert haben (uninitialisiert sind), wenn sie gelesen werden, das stellt der Compiler sicher.

    Mir ist neu (war neu), dass in C++ die Member vor Aufruf des Ctors default initialisiert werden. In Java kann es keine Objekte geben, die nicht mit new/Ctor angelegt wurden. Das ist vermutlich der Unterschied.



  • Moin 😉
    Ja das ist unschön aber c++ verhält sich da korrekt. Mein Workaround: Lass den legacy Konstruktor einfach weg und erstelle eine custom Ersatzmethode in der Basisklasse und die wird ganz normal vererbt. Einziger Nachteil damit ist daß diese Methode extra aufgerufen werden muß.

    SubClass *res = new SubClass; // hier nichts übergeben
    res->inst(foo, bar); // rufe den Ersatzkonstruktor
    

    MFG


  • Gesperrt

    Dann is es aber keine Komposition im UML-sinne mehr...


Anmelden zum Antworten