Übergabe einer Klassen Instanz an Funktion . Copy C'tor ?



  • Hallo Leute ich hatte grade ein merkwürdiges Problem . Welches ich nicht so richtig verstehe. Und zwar habe ich einen C'tor für die Klasse LINIE implementiert der 2 Punkte vom Typ POINT erwartet . Bei der Übergabe einer selbst definierten Klasse an eine Funktion wird ja bekanntlicher Weise der Copy C'tor aufgerufen . Diesen habe ich anschließend implementiert und dachte nun eigentlich dass das Programm laufen sollte . Tat es aber nicht , denn ich bekam folgende Fehlermeldung : No Matching Function for call to Point::Point() in der gekennzeichneten Zeile . Anschließed habe ich den Standard C'Tor implementiert und das Programm lief ohne Probleme . Selbst wenn ich den Copy C'tor aus dem Programm entferne meckert der Compiler nicht.
    Habe ich etwas übersehen oder warum schreit mein Compiler nach dem Standard Konstruktor , und wiso kompiliert er das Programm auch ohne Copy C'tor.
    Es wäre echt super wenn mich da jemand aufklären könnte .

    Folgender Code :

    //#include <iostream>
    #include "Point.h"
    #include "Line.h"
    using namespace std;
    int main (int argc, char * const argv[]) {
    
        std::cout << "Hello, World!\n";
    	Point X(7,8);
    	Point Y(9,9);
    	Line _Line(X,Y);
        return 0;
    }
    
    #ifndef _POINT_
    #define _POINT_
    #include <iostream>
    
    class Point 
    {
    	int x;
    	int y;
    
    public:
    	Point();
    	Point(const Point& _Point);
    	Point(int _x , int _y);
    	~Point();
    	void setX(int _x);
    	void setY(int _y);
    	int getX() const;
    	int getY() const;
    
    };
    #endif
    
    #include "Point.h"
    using namespace std;
    
    Point::Point()
    {
    }
    
    Point::Point(const Point& _Point)
    {
    	x = _Point.getX();
    	y = _Point.getY();
    }
    
    Point::Point(int _x , int _y)
    {
    	x = _x;
    	y = _y;
    }
    Point::~Point()
    {
    	cout << "D'tor Point " << endl;
    }
    void Point::setX(int _x)
    {
    	x = _x;
    }
    void Point::setY(int _y)
    {
    	y = _y;
    }
    int Point::getX() const
    {
    	return x;
    }
    int Point::getY() const
    {
    	return y;
    }
    
    #include "Point.h"
    
    class Line
    {	
    	Point X;
    	Point Y;
    
    	public:
    	Line(Point _X, Point _Y);
    	~Line();	
    	void setPointX(int _x , int _y);
    	void setPointY(int _x , int _y);
    	Point getPointX() const;
    	Point getPointY() const ;
    
    };
    
    #include "Line.h"
    using namespace std;
    
    Line::Line(Point _X, Point _Y) // HIER TAUCHTE DER FEHLER AUF 
    {
    	X.setX(_X.getX());
    	X.setY(_X.getY());
    	Y.setX(_Y.getX());
    	Y.setY(_Y.getY());
    }
    Line::~Line()
    {
    	cout << "Line D'tor" << endl;
    }
    
    void Line::setPointX(int _x , int _y)
    {
    	X.setX(_x);
    	X.setY(_y);
    }
    void Line::setPointY(int _x , int _y)
    {
    	Y.setX(_x);
    	Y.setY(_y);
    }
    Point Line::getPointX() const
    {
    	return X;
    }
    Point Line::getPointY() const
    {
    	return Y;
    }
    


  • Bacid90210 schrieb:

    #include "Line.h"
    using namespace std;
    
    Line::Line(Point _X, Point _Y) // HIER TAUCHTE DER FEHLER AUF 
    {
    	X.setX(_X.getX());
    	X.setY(_X.getY());
    	Y.setX(_Y.getX());
    	Y.setY(_Y.getY());
    }
    

    Da du keine Initialisierungslisten verwendest (solltest du tun) werden die member X und Y (vom Typ Point) bevor der body des Konstruktors durchlaufen wird erstmal vom default-Konstruktor erstellt.
    Wenn du selber einen oder mehrere Konstruktor(en) für Point angegeben hast (auch der CCTOR zählt hierzu) wird kein default-Konstruktor mehr vom compiler generiert. Wenn nun kein Default-Konstrukor vorhanden ist, weil du keinen anbietest, können X und Y nicht erstellt werden -> und genau das sagt deine Fehlermeldung.

    Du könntest entweder dem parametrisiertem Konstruktor von Point default-Werte zuweisen (damit würde es ein default-Konstruktor sein - aufruf ohne Argumente wäre möglich) oder du verwendest eine Initialisierungsliste wie folgt:

    #include "Line.h"
    using namespace std;
    
    Line::Line(Point _X, Point _Y) :X(_X), Y(_Y) {}
    

    Damit nutzt du den CCTOR von Point.
    Hinweis zum CCTOR von Point: Du kannst natürlich direkt die member ansprechen und musst nicht die getter nutzen. Besser wäre auch hier wieder: Initialisierungsliste.
    Und da du für Point schon einen default constructor angibst, solltest du dort die member initialisieren (also x und y).


  • Mod

    Kopierkonstruktor, Standardkonstruktor, Kopieroperator und Destruktor einer Klasse werden automatisch erzeugt wenn nötig und keine alternative Deklaration gefunden wurde. Als du zunächst den Kopierkonstruktor selber geschrieben hast, konnte kein automatischer Standardkonstruktor mehr erzeugt werden, da schon ein anderer Konstruktor vorhanden war. Dadurch konnten die Point-Member von Line nicht mehr erzeugt werden, da du keine Initialisierunglisten benutzt die deinen eigenen Konstruktor benutzen ( ⚠ mach dich unbedingt schlau, was eine Initilisierungsliste ist. Das ist ungeheuer wichtig und du weißt offensichtlich nicht darüber Bescheid.). Als du dann den Standardkonstrukotr selbst implementiert hast, hat dein Compiler alle von dir benutzten Konstruktoren vorgefunden, den Kopierkonstruktor für die Parameterübergabe an Funktionen und den Standardkonstruktor zum Erzeugen der Member von Line. Daher funktionierte der Code. Als du dann den Kopierkonstruktor wieder entfernt hast, hat der Compiler einfach automatisch einen erzeugt (die Präsenz anderer Konstruktoren verhindert nicht das automatische Erzeugen eines Kopierkonstruktors) und dein Code hat weiterhin funktioniert. Würdest du den Standardkonstruktor ebenfalls entfernen, so würde dieser auch wieder automatisch erzeugt und dein Programm liefe immer noch.
    ➡ Du brauchst die Konstruktoren von Point gar nicht. Ebensowenig den Destruktor. Gleiches gilt für den Destruktor von Line. Kopierkonstruktor, Kopieroperator und Destruktor braucht man eigentlich nur, wenn die Klasse nichttriviale Ressourcen verwaltet, zum Beispiel Speicher selber anfordert oder selber Dateien offen hält. Und dann braucht man auch alle Drei auf einmal, das nennt man dann die "Regel der großen Drei".
    Eigene Konstruktoren (außer dem Kopierkonstruktor) sind hingegen ganz normal, so wie dein Konstruktor für Line. Bloß eine Initialisierungsliste sollte er noch benutzen.



  • Ok , das heißt ich benötige den Default Konstruktor der Klasse Point weil beim Aufruf des Konstruktors der Klasse Linie erst einmal zwei Instanzen der Klasse Point vom Default Konstruktor erstellt werden . Anschließend werden die Werte dann über den entweder selbst definierten oder falls dieser nicht implementiert ist , über den default copy Konstruktor zugewiesen ?

    Habe ich das jetzt richtig verstanden .



  • Den Konstruktor der Klasse Point benötige ich doch aber beim erstellen von Instanzen innerhalb der Main Funktion oder nicht ?


  • Mod

    Bacid90210 schrieb:

    Ok , das heißt ich benötige den Default Konstruktor der Klasse Point weil beim Aufruf des Konstruktors der Klasse Linie erst einmal zwei Instanzen der Klasse Point vom Default Konstruktor erstellt werden . Anschließend werden die Werte dann über den entweder selbst definierten oder falls dieser nicht implementiert ist , über den default copy Konstruktor zugewiesen ?

    Habe ich das jetzt richtig verstanden .

    Nein. Beim Aufruf des Konstruktors von Line werden die Konstruktoren der Member aufgerufen (hier deine beiden Points), und zwar der Konstruktor der Initilisierungsliste steht. Erst danach wird das ausgeführt was tatsächlich im Konstruktor von Line steht, bei dir die ganzen setX/setY. Wenn du keine Initialisierungsliste angibst, müssen die Member trotzdem irgendwie erzeugt werden (sonst könntest du sie im Funktionskörper nicht benutzen) und dann wird mangels genauerer Information was gemacht werden soll der Standardkonstruktor benutzt.

    Man sieht daher übrigens sehr oft Konstruktoren die einfach nur aus einer Initialisierungsliste für die Member bestehen und ansonsten einen leeren Funktionskörper haben. Denn die meisten Konstruktoren sollen nur die Member passend initialisieren, es ist eher die Ausnahme, dass im Funktionskörper des Konstruktors echte Arbeit verrichtet wird.



  • Achso und eine Frage noch .
    Nutze ich nur meinen eigenen Copy Konstruktor wenn ich eine Initialisierungsliste implementiert habe . Ich dacht bei der Übergabe an di Funktion geschieht dies automatisch 😕 ?


  • Mod

    Bacid90210 schrieb:

    Den Konstruktor der Klasse Point benötige ich doch aber beim erstellen von Instanzen innerhalb der Main Funktion oder nicht ?

    Ahh, den hatte ich übersehen. Ja, den brauchst du und der ist auch sinnvoll.

    Merke: Die Anwesenheit irgendeines Konstruktors verhindert die automatische Erzeugen eines Standardkonstruktors, aber die Anwesenheit anderer Konstruktoren verhindert nicht die automatische Erzeugung eines Kopierkonstruktors.


  • Mod

    Bacid90210 schrieb:

    Achso und eine Frage noch .
    Nutze ich nur meinen eigenen Copy Konstruktor wenn ich eine Initialisierungsliste implementiert habe . Ich dacht bei der Übergabe an di Funktion geschieht dies automatisch 😕 ?

    Es kann nur einen Kopierkonstruktor geben. Ja, dieser wird bei Übergabe von Funktionsargumenten benutzt. Dabei ist es egal, ob das dein eigener oder ein automatisch erzeugter Konstruktor ist.
    Das bezieht sich aber nur auf das Übergeben der Funktionsargumente an den Konstruktor. Die Erzeugung der Member hat damit überhaupt nichts zu tun, das ist ein anderer Schritt.

    P.S.: Die Abfolge von Fragen und Antworten in diesem Thread muss für Außenstehende sehr verwirrend sein 😃 .



  • Ok ,Danke schön schon mal für deine Hilfe .
    Ich fasse also nochmal zusammen .

    • Für Klassen die nur Standard Typen wie integer usw beinhalten benötige ich nicht zwingend einen eigenen Konstruktor , da mir der Compiler automatisch einen erstellt der die Zuweisungen für mich erledigt .

    •Den Default Konstruktor muss ich auch nicht extra implementieren , dies tut ebenfalls der Compiler für mich.

    •Bei der Übergabe von selbst definierten Typen an eine Methoden/Funktionen wird der Copy Konstruktor aufgerufen.

    Eine letzte Frage habe ich aber noch .

    Und zwar den genauen Ablauf dieser Zeile bei Programm ausführung.

    Line::Line(Point _X , Point Y):X(_X),Y(_Y)
    

    1.Erzeugen zweier Instanzen vom Typ Point per default Konstruktor für meine neu erzeugte Instanz Linie.(Deswegen wurde der default Konstruktor von Point benötigt)
    2.Aufruf der Copy Konstruktoren für die einzelnen Punkte mit Übergabeparameter _X und _Y
    😕

    Boah es ist auch schon echt spät ...
    Ich hoffe ich hab das jetzt richtig verstanden .



  • Das heißt ja dann eigentlich auch dass der Konstruktor einer Klasse alle Konstruktoren seiner Member aufruft oder ?



  • ja



  • Bacid90210 schrieb:

    Ok ,Danke schön schon mal für deine Hilfe .
    Ich fasse also nochmal zusammen .

    • Für Klassen die nur Standard Typen wie integer usw beinhalten benötige ich nicht zwingend einen eigenen Konstruktor , da mir der Compiler automatisch einen erstellt der die Zuweisungen für mich erledigt .

    •Den Default Konstruktor muss ich auch nicht extra implementieren , dies tut ebenfalls der Compiler für mich.

    •Bei der Übergabe von selbst definierten Typen an eine Methoden/Funktionen wird der Copy Konstruktor aufgerufen.

    Eine letzte Frage habe ich aber noch .

    Und zwar den genauen Ablauf dieser Zeile bei Programm ausführung.

    Line::Line(Point _X , Point Y):X(_X),Y(_Y)
    

    1.Erzeugen zweier Instanzen vom Typ Point per default Konstruktor für meine neu erzeugte Instanz Linie.(Deswegen wurde der default Konstruktor von Point benötigt)
    2.Aufruf der Copy Konstruktoren für die einzelnen Punkte mit Übergabeparameter _X und _Y
    😕

    Boah es ist auch schon echt spät ...
    Ich hoffe ich hab das jetzt richtig verstanden .

    stopp dein erster punkt ist falsch

    es ist so:
    standard construktor ist das ding was keine parameter hat
    copy ctor ist der der die eigene klasse als parameter hat

    so wenn du gar keinen contruktor schreibst, dann generiert der compiler dir den default und den copy constructor, wobei
    - der generierte default konstruktor ruft einfach nur alle standard konstruktoren der member auf (bei den build in typen werden diese mit irgendwelchen beliebigen zahleng gefüllt)
    - der generierte copy ctor kopiert jeden wert der member in das andere objekt (deshalb ist das mit zeigern auch mies weil die addresse im speicher einfach nur kopiert wird)

    so nun kann es aber sein dass deine klasse den standard ctor verbieten will (weil die z.b. in jedem fall parameter braucht), dann kannst du den deklararieren aber nicht implementieren (hagelt linker fehler) oder du schreibst ihn gar nicht, implementierst aber dafür einen anderen Ctor der parameter entgegen nimmt

    merke: sobald ein konstruktor geschrieben ist (ausser dem Copy Ctor) generiert der compiler keinen default kontruktor mehr...

    class A
    {
        int m;
    public:
        A(float f)  // ein Ctor ist geschrieben, der default CTor wird nicht mehr generiert
        {
           this->m = static_cast<int>(f);
        }
        A(bool b = false) // setzt man hier default werte ein, könnte mand en konstruktor wie den standardkonstruktor aufrufen (isser aber nicht!)
        {
            m = b ? 1 : -1;
        }
    };
    
    class B // hier wird der default(und nur der!) konstruktor vom compiler generiert
    {
        int m1;
        int m2;
    
    public:
        B(B const& other) // nicht nötig da der standard genereirte copy contruktor auch nur die daten kopiert
        {
            this->m1 = other.m1;
            this->m2 = other.m2;
        }
    
        void method(...)
        { ... }
    };
    

    initialisierungslisten sind dafür da, um die member zu initialisieren.
    normaler weise ist es so, dass dein objekt erstellt wird im speicher, die member haben dann zufällige werte die halt im speicher stehen.
    dann erst(im CTor body) werden deinen membern werte zugewiesen.
    das kostet alles zeit und funktioniert mit constanten und referenzen schonmal nicht. die initialisierunslsite greift kurz nach dem speicher bereitstellen und schiebt dann sofort werte in deine member (ist schneller und man kann so auch constanten initialisieren)

    bsp:

    class Foo
    {
        const int c;
        int & r;
    
    public:
        Foo(int constante, int loliboy)
         : c(constante), r(loliboy) // gut so
        {    }
    
        Foo(float cf, int l)
        {
            this->c = static_cast<int>(cf); // fehler: konstante werte kann man nicht verändern blablabla
            this->r = l; // referenzen müssen beim erstellen initialisiert werden, hier würdest du den speicher der hinter der referenz ist schon verändern
        }
    ];
    

    hoffe das ist was klarer



  • Einige Anmerkungen zum letzten post von Skym0sh0:

    Ein default-constructor ist entweder ein Konstruktor ohne Parameter oder ein Konstruktor mit Parametern, wobei dann für jeden Parameter ein default-Wert angegeben wurde.
    Somit IST

    A:A(bool b = false) {
        // code
    }
    

    aus dem ersten code-Beispiel ein default-constructor. Das hast du falsch dargestellt.

    Wenn man einen eigenen Konstruktor angibt, dann wird wie jetzt schon oft erwähnt kein default-construktor mehr generiert. Das gilt auch wenn man einen copy-constructor angibt -> der default-constructor wird nicht generiert.
    Daher wird für Klasse B des ersten code-Beispiels KEIN default-constructor generiert.


Log in to reply