OOP - "... does not have class type"



  • Hallo liebe Communiy!

    Folgender Code führt beim Kompilieren zu diesen Fehlermeldungen:

    C:\hiwi\projekt\Show\main.cpp||In constructor 'CLine::CLine(CPoint, CPoint)':|
    C:\hiwi\projekt\Show\main.cpp|48|error: no matching function for call to 'CPoint::CPoint()'|
    
    C:\hiwi\projekt\Show\main.cpp||In function 'int main()':|
    C:\hiwi\projekt\Show\main.cpp|72|error: 'linie.CLine::GetA' does not have class type|
    
    #include <cmath>
    #include <iostream>
    
    #define PI 3.14159265
    
    using namespace std;
    
    class CPoint
    {
    	public:
    		CPoint(unsigned long nX, unsigned long nY);
    		unsigned long GetX() const;
    		unsigned long GetY() const;
    	private:
    		unsigned long x;
    		unsigned long y;
    };
    
    CPoint::CPoint(unsigned long nX, unsigned long nY)
    {
    	x = nX;
    	y = nY;
    }
    
    unsigned long CPoint::GetX() const
    {
    	return x;
    }
    
    unsigned long CPoint::GetY() const
    {
    	return y;
    }
    
    class CLine
    {
        public:
            CLine(CPoint a, CPoint b);
            CPoint GetA() const;
            CPoint GetB() const;
        private:
            CPoint A;
            CPoint B;
    
    };
    
    CLine::CLine(CPoint a, CPoint b)
    {
        A = a;
        B = b;
    }
    
    CPoint CLine::GetA() const
    {
        return A;
    }
    
    CPoint CLine::GetB() const
    {
        return B;
    }
    
    int main()
    {
        CPoint p1(1,2);
        CPoint p2(3,4);
        CLine linie(p1,p2);
    
        cout << "X-Koordinate Punkt A: " << linie.GetA.GetX() << endl;
        cout << "Y-Koordinate Punkt A: " << linie.GetA.GetY() << endl;
        cout << "X-Koordinate Punkt B: " << linie.GetB.GetX() << endl;
        cout << "Y-Koordinate Punkt B: " << linie.GetB.GetY() << endl;
    
        return 0;
    }
    

    Vielen Dank fürs Lesen und eine gute Nacht 🙂
    lyrichter


  • Mod

    C:\hiwi\projekt\Show\main.cpp||In constructor 'CLine::CLine(CPoint, CPoint)':|
    C:\hiwi\projekt\Show\main.cpp|48|error: no matching function for call to 'CPoint::CPoint()'|
    

    Dein CLine enthält 2 Objekte vom Typ CPoint. CPoint besitzt keinen Standardkonstruktor (das ist ein Konstruktor ohne Argumente), daher musst du beim Erstellen des CLine angeben, welchen Konstruktor du für die CPoint-Member benutzen möchtest. Innerhalb des Konstruktors ist es dazu zu spät, da müssen die Member bereits initialisiert sein. Benutze daher eine Initialisierungsliste. Das hier macht das was du mit deinem jetzigen Konstruktor bezwecken willst, aber richtig:

    CLine::CLine(CPoint a, CPoint b): A(a), B(b){}
    

    C:\hiwi\projekt\Show\main.cpp||In function 'int main()':|
    C:\hiwi\projekt\Show\main.cpp|72|error: 'linie.CLine::GetA' does not have class type|

    GetA ist eine Funktion und kennt daher keinen Memberzugriffsoperator (Den kennen nur Klassen, das möchte dir die Fehlermeldung sagen). Willst du die Funktion aufrufen (und so das Objekt vom Klassentyp CPoint erhalten), musst du sie natürlich erst aufrufen:

    linie.GetA().GetX()
    

    P.S.: Du solltest generell Initialisierungslisten wann immer es geht benutzen. So auch beim Konstruktor von CPoint.



  • Danke, das Programm lässt sich nun ohne Fehler kompilieren.

    GetA ist eine Funktion

    Autsch, das lag wohl an der Uhrzeit.

    CPoint besitzt keinen Standardkonstruktor (das ist ein Konstruktor ohne Argumente)

    Stimmt, den hatte ich u.a. für das Posting aus dem Original-Programm herausgeschnitten. Hatte mich schon gewundert, warum ich jetzt plötzlich einen Fehler mehr habe.

    Im Originalprogramm habe ich aber ebenfalls keine Initialisierungsliste verwendet, sondern einfache Zuweisungen:

    CPoint::CPoint()
    {
    	x = 0;
    	y = 0;
    }
    

    Ist auch bei Vorhandensein eines Standard-Konstruktors eine Initialisierungslsite zu empfehlen?

    Außerdem verstehe ich noch nicht, wieso diese Änderung mein Problem löst (auch wenn se es unbestreitbar tut ;)).
    Wenn ich das richtig verstanden habe, verlangt der Compiler an dieser Stelle

    CLine::CLine(CPoint a, CPoint b)
    {
        A = a;
        B = b;
    }
    

    nach einem Konstruktor für die Klasse CPoint.
    Wieso reicht hier aber nicht mein definierter Konstruktor aus, zumal es ja nur einen gibt (und sich die Auswahl daher erledigt)? Und an welcher Stelle genau versucht er, einen Konstrutkro zu finden - bei den Zuweisungen oder in den Klammern?

    Und wie teilt nun die Initialisierungsliste dem Compiler mit, welchen Konstruktor er benutzen soll? Liest sich die Zeile nicht einfach so:
    "Initialisiere A mit dem Wert von a!" ? Ist die Frage nach dem Konstruktor von a dann nicht immer noch offen?

    Viele Grüße,
    lyrichter


  • Mod

    lyrichter schrieb:

    Ist auch bei Vorhandensein eines Standard-Konstruktors eine Initialisierungslsite zu empfehlen?

    Ja. Wozu erst Standardinitialisieren und danach eine Zuweisung machen, wenn du auch gleich den passenden Konstruktor hättest verwenden können? So sparst du dir die Hälfte des Rechenaufwandes.

    Außerdem ist dies auch was andere Programmierer erwarten, wenn sie deinen Code lesen. Und es ist konsistenter, denn früher oder später wirst du Klassen ohne Standardkonstruktor benutzen. Dann ist es schöner, wenn alles in der Initialisierungsliste steht.

    Außerdem solltest du dir Gedanken über die Sinnhaftigkeit deiner Standardkonstruktoren machen. Ist es wirklich eine Eigenschaft eines Punktes, standardmäßig den Wert (0,0) zu haben? Gibt es irgendeine sinnvolle Anwendung dieses Konstruktors? Wird der Code irgendwie besser zu lesen, wenn der Anwender Cpoint foo; anstatt CPoint foo(0,0); schreiben darf? Ich denke die Antwort auf alle diese Fragen ist Nein.

    Was am ehesten Sinn macht, wäre ein Standardkonstruktor der dann aber keine Werte zuweist, sondern x und y uninitialisiert lässt.

    P.S.: Und dein jetziger Standardkonstruktor ist übrigens äquivalent dazu, wenn du bei deinem anderen Konstruktor einfach Defaultparameter angibst. Dann hast du nur eine Funktion die dann im Notfall auch nur die Hälfte der Wartungsarbeit verursacht. Etwa wenn du deinen Punkt dreidimensional machst, müsstest du jetzt 2 Stellen ändern, wenn du Defaultparameter benutzt nur eine.



  • SeppJ schrieb:

    Was am ehesten Sinn macht, wäre ein Standardkonstruktor der dann aber keine Werte zuweist, sondern x und y uninitialisiert lässt.

    Du meinst so etwas?

    CPoint::CPoint()
    {
    }
    

    Warum ist diese Variante am sinnvollsten? An welcher Stelle wird denn ein solcher Standard-Konstruktor benötigt bzw. was ist allgemein der Zweck von Standard-Konstruktoren, die keine Member initialisieren? Ich dachte, gerade das wäre ihre Aufgabe 😕

    Und bei dem Standard-Konstruktor von CLine? Ist auch dort

    CLine::CLine()
    {
    }
    

    am sinnvollsten?

    Viele Grüße,
    lyrichter



  • lyrichter schrieb:

    Warum ist diese Variante am sinnvollsten?

    Weiß nicht. Ich würde mir streng überlegen, ob ein Standardkonstruktor überhaupt benötigt wird. Ich mag diese Biester nicht. Aber naja, bei Point und Line würde ich ein Auge zudrücken.

    lyrichter schrieb:

    An welcher Stelle wird denn ein solcher Standard-Konstruktor benötigt bzw. was ist denn der Zweck von Standard-Konstruktoren, die keine Member initialisieren überhaupt?

    Damit man ein Array anlegen kann.

    Point sechseck[6];
    for(int i=0;i<6;++i)
       sechseck[i]=Point(mx+r*cos(M_PI*2/i+phi),mx+r*sin(M_PI*2/i+phi));
    


  • SeppJ schrieb:

    CPoint besitzt keinen Standardkonstruktor (das ist ein Konstruktor ohne Argumente), daher musst du beim Erstellen des CLine angeben, welchen Konstruktor du für die CPoint-Member benutzen möchtest.

    Aber auch an dieser Stelle (s. Quelltext oben) scheint ein Standard-Konstruktor ja eine Rolle zu spielen. Allerdings ist mir noch nicht klar, wieso hier ein Standard-Konstruktor vonnöten ist bzw. wie er das Problem löst.


  • Mod

    Wenn ein Objekt erzeugt wird, dann wird in folgender Reihenfolge vorgegangen:
    1. Konstruktor eventueller Basisklassen aufrufen
    2. Konstruktoren der Member aufrufen, in der Reihenfolge wie sie in der Klassendefinition stehen
    3. Eigentlichen Code im Konstruktor ausführen

    Mittels der Initialisierungsliste kann man bei 1 und 2 angeben welcher Konstruktor mit welchen Argumenten benutzt werden soll. Wenn man nichts angibt, bleibt logischerweise nur die Möglichkeit, einen Konstruktor ohne Argumente zu nehmen, den Standardkonstruktor. Wenn es keinen gibt (oder er nicht sichtbar ist), dann geht das nicht und es gibt den Fehler nach dem du in deinem ersten Beitrag gefragt hast.



  • Danke, jetzt habe ich alles verstanden! 🙂

    Ist es in C++ üblich bzw. guter Stil, Standard-Konstruktoren zu definieren, die nichts initialisieren?


  • Mod

    lyrichter schrieb:

    Ist es in C++ üblich bzw. guter Stil, Standard-Konstruktoren zu definieren, die nichts initialisieren?

    Nein. Es ist üblich, dass ein Konstruktor ein Objekt erstellt, mit dem man sofort arbeiten kann. Wenn dies ohne zusätzliche Information nicht möglich ist, dann bietet man eben keinen Standardkonstruktor an. Mein Kommentar bezog sich eher auf deine willkürliche Nullinitialisierung, durch die überhaupt nichts gewonnen wird. Da kannst du dann auch gleich alles uninitialisiert lassen.


Anmelden zum Antworten