Argumentloser Konstruktor



  • Hey Leute,

    ich habe gerade erst angefangen mich mit cpp genauer zu beschäftigen. Seht es mir daher bitte nach, wenn ich etwas falsch verstanden habe (und korrigiert mich natürlich xD).

    Zu meiner Frage:
    Ich habe eine Klasse A mit einem Konstruktor, welcher Argumenta a, b, c benötigt. Nun habe ich eine Klasse B geschrieben, welche in Ihrem Konstruktor eine Instanz von A übergeben bekommt. Dabei bekam ich einen Fehler der mich Zwang für A einen argumentlosen Konstruktor zu schreiben. Meine Frage ist nun, warum ein solcher argumentloser Konstruktor notwendig ist bzw wofür der Kompiler ihn braucht.

    Viele Grüße
    NPC



  • Ich vermute, du hast den zweiten Konstruktor falsch definiert...

    Aber ohne Code zu sehen, braucht man ein Glaskugel. 😞



  • @NPC Zeig Code!

    Ich nehme an, dass du irgendwo eine Default-Initialisierung benötigst, z.B. wenn du einen vector<A> hast oder eine Variable A irgendwas; erzeugst.

    Was bedeutet "welche in Ihrem Konstruktor eine Instanz von A übergeben bekommt" genau? Wie funktioniert die Übergabe? Per Value? Fehlt dir ein Copy-Construktor? Per (konstanter) Referenz oder per Pointer? Dann sollte nichts davon nötig sein.



  • Hi,
    sorry ich dachte da es um keinen konkreten Fehler als viel mehr ein Kompiler-Verhalten geht wäre Code nicht notwendig. Hier mal kurz ein minimal Code:

    in header:

    class A {
    public:
    	int a;
    	A(int a_);
    };
    
    class B {
    public:
    	A a;
    	B(A a_);
    };
    

    in source file:

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

    Fehler (by vs):

    Fehler (aktiv)	E0291	Für die Klasse ""A"" ist kein Standardkonstruktor vorhanden.
    

    Der Fehler tritt in B::B(A a_) auf bzw. wird dort gezeigt.
    Lösung des Fehlers durch Einführung eines attributlosen Konstruktors (in source and header):

    A::A() {
    	a = 1;  // dummy value
    }
    

    Frage:
    Warum genau ist das auf Seite des Kompilers notwendig einen attributlosen Konstruktor zu haben? (Sofern das überhaupt notwendig ist und ich hier nicht etwas falsch verstanden habe)



  • Im Header hast du geschrieben:

    @NPC sagte in Argumentloser Konstruktor:

    B(A a_);

    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.

    Workaround: Nimm ne Referenz.



  • @NoIDE
    Ah dankeschön. Ja das macht natürlich absolut sinn :).

    @wob jetzt verstehe ich deinen Kommentar auch. Auch an dich ein Dankeschön :).

    Viele Grüße
    NPC



  • Ich bin mir aber nicht zu 100 % sicher, dass die Fehlermeldung/Warnmeldung auch tatsächlich daran liegt. Habe das lediglich logisch hergeleitet. Für mich ergibt das Sinn.



  • @NoIDE Die Fehlermeldung ist nach der Verwendung einer Referenz verschwunden und der Code läuft anstandslos. Daher sieht es gut aus 🙂



  • Anstatt eines Workarounds mit Referenz kann man das besser so machen:

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

    Dann wird a direkt mit dem richtigen Ctor erstellt und man braucht keinen leeren Ctor um in ein leeres Objekt rein zu kopieren.

    P.S. Wie sieht denn dein Code mit Referenz aus?



  • Dieser Beitrag wurde gelöscht!


  • @NoIDE sagte in Argumentloser Konstruktor:

    Im Header hast du geschrieben:

    @NPC sagte in Argumentloser Konstruktor:

    B(A a_);

    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.

    Workaround: Nimm ne Referenz.

    Sorry, aber das ist falsch.
    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), sofern man keine Initialisierungsliste verwendet, d.h. hierfür würde dann der Standardkonstruktor benötigt.
    Und selbst bei einer Parameterübergabe als Referenz käme es, ohne die Initialisierungsliste, zu der Fehlermeldung!

    @NoIDE: Du solltest zuerst C++ richtig erlernen, bevor du irgendwelche Mutmaßungen anstellst.

    @NPC: Hast du den Member a der Klasse B als Referenz erstellt (also A& a)?



  • Hi @Th69,

    vielleicht habe ich das mit der Referenz falsch verstanden aber ich habe nun einfach einen Pointer zu dem breits erstellten Object von A übergeben (in der header-Datei natürlich entsprechend geändert).

    B::B(A* a_){
    
    }
    

    und das läuft durch.

    Edit: Nvm von A& a habe ich verstanden was du mit Referenz meinst... Keine Ahnung ob das klappt



  • @NPC sagte in Argumentloser Konstruktor:

    vielleicht habe ich das mit der Referenz falsch verstanden aber ich habe nun einfach einen Pointer zu dem breits erstellten Object von A übergeben

    Ist ja schön, dass es funktioniert, aber die Parameterübergabe sollte so sein, wie es semantisch sinnvoll ist. Pointer erlaubt es auch, nullptr zu übergeben. Willst du das? Und die zweite Frage ist: wenn du den Pointer als Member von B speichern solltest (und ihn nicht nur im Konstruktur verwendest), dann ist das ein Rezept für Unglück, weil du dich dann um Ownership kümmern müsstest. Also: willst du wirklich einen Pointer übergeben?



  • 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ß.


Anmelden zum Antworten