Basisinitialisierer Vererbung



  • Ich würde im Fall von "zwei einfache Klassen, welche public vererbt werden" von der "Ableitung" der einen Klasse von einer Basisklasse sprechen.
    Zusätzlich zu @wob habe ich noch hinzuzufügen:
    Da die Klasse Rectangle auch den Konstruktor Shape2D(double x, double y) erbt, könntest Du diesen in Rectangle überschreiben, um ggf. die Attribute Rectangle::heigth und Rectangle::width mit gültigen Werten zu initialisieren.



  • @C-Sepp sagte in Basisinitialisierer Vererbung:

    Ich habe zwei einfache Klassen, welche public vererbt werden...Nun muss man in diesen Fall für den Konstruktor der abgeleiteten Klasse ja den Konstruktor der Basisklasse als Basisinitialiserer mit angeben

    Um das klar zu stellen: das hat nichts mit der public-Vererbung zu tun, sondern damit, dass die Basisklasse keinen default-Konstruktor hat (weil du ja einen speziellen Konstruktor erstellt hast). Ohne default-Konstruktor kannst du halt kein default-Objekt erstellen - auch eine abgeleitete Klasse kann ohne den passenden Konstruktor sein parent nicht default erstellen.



  • Alles klar. Bei der folgenden Klassenhierarchie mit diversen Membervariablen und Konstruktoren hat sich jetzt
    noch eine Frage ergeben:

    class Address
    {
    private:
    	string name, street, city, country;
    public:
    	Address()
    	{
    		city = "";
    		country = "";
    		name = "";
    		street = "";
    	}
    
    	Address(string& nm, string& str, string& cty, string ctry = "Deutschland") : name(nm), street(str), city(cty), country(ctry)
    	{}
            //...
           //diverse Zugriffsmethoden
           //...
    }
    
    class Mail
    {
    	protected:
    		int id;
    		Address from, to;
    		bool delivered;
    	public:
    		Mail(int i): id(i), delivered(false){}		
    
    		Mail(int i, Address frm, Address t): id(i), from(frm), to(t), delivered(false){}
    
    class Parcel : public Mail
    {
    	private:
    		bool insured;
    		float weigth;		
    
    	public:		
    		Parcel(int i, float wgt, Address& t, bool ins= false, Address frm = Address()) :Mail(i, frm, t), insured(ins),    weigth(wgt)  {}
    
    		Parcel(int i, float wgt, bool ins=false): Mail(i), weigth(wgt), insured(ins){}		
    
    
    
    


  • @C-Sepp

    Aha, und welche?



  • Bei der Klasse Mail Mail fehlen selbstverständlich noch die schließenden geschweiften Klammern. Bei Klasse Address und Mail noch das Semikolon. Hier die Fortsetzung

    class Parcel : public Mail
    {
    	private:
    		bool insured;
    		float weigth;		
    	public:		
    		Parcel(int i, float wgt, Address& t, bool ins= false, Address frm = Address()) :Mail(i, frm, t), insured(ins),   weigth(wgt)  {}
    
    		Parcel(int i, float wgt, bool ins=false): Mail(i), weigth(wgt), insured(ins){}	
                     //...
                     //diverse Zugriffsmethoden
                     //...
    };
    
    class TraceParcel: public Parcel
    {
    	class Stamp
    	{
    		public:
    			time_t t;
    			string location;
    
    			Stamp() : t(0){}
    
    			void setStamp(string lct)
    			{
    				location = lct;
    				t = time(NULL);
    			}
    	};
    	Stamp timestamps[10];
    	int quantity=0;	
    
    	public:		
    		TraceParcel(const Parcel& pc): Parcel(pc)
    		{			
    			timestamps[0].setStamp(from.getCity());
    		}
    
                    //...
                    //diverse Zugriffsmethoden
                    //...
    } ;              
    

    Wie man erkennen kann, wird im Konstruktor der Klasse TraceParcel der Basisintialisierer mit einem Objekt vom Typ Parcel initialisiert. Warum bezieht sich der Basisinitialisierer im Konstruktor einer abgeleiteten Klasse eigentlich immer nur auf die In der Klassenhierarchie vohergehende Klasse, in dem Fall Parcel?



  • Probehalber habe ich den Konstruktor von TraceParcel auch mal anders aufgebaut:
    Wenn ich:

    	TraceParcel(const Parcel& pc, Address adr): Parcel(1, 2, adr )
            //...
    

    bekomme ich keinen Fehler, bei:

           TraceParcel(const Parcel& pc, Address adr): Parcel(1, 2, Address())
            //...
    

    kommt hingegen ein Fehler. Warum? Vielen Dank!



  • @C-Sepp sagte in Basisinitialisierer Vererbung:

    class Address
    {
    private:
    	string name, street, city, country;
    public:
    	Address()
    	{
    		city = "";
    		country = "";
    		name = "";
    		street = "";
    	}
    
    	Address(string& nm, string& str, string& cty, string ctry = "Deutschland") : name(nm), street(str), city(cty), country(ctry)
    	{}
    

    Hier erstmal Stop! Was willst du mit den Zeilen 8 bis 11 bezwecken? Das sind std::strings, die sind per Default leer. Lass die Zeilen komplett weg! Und wenn du die manuell initialisieren wolltest, dann solltest du das auch tun - und zwar über Address() : name("default name"), street("Hauptstr."), city("Stadt"), country("DE") {} (so hast du es ja auch beim 2. Konstruktor gemacht)

    Und Punkt 2 ist deine Zeile 14. Warum sind die Parameter nm, str und cty nicht-konstante Referenzen? Das ergibt soch gar keinen Sinn!



  • @C-Sepp sagte in Basisinitialisierer Vererbung:

    kommt hingegen ein Fehler. Warum? Vielen Dank!

    Welcher denn?

    Und: beantworte mal meine Frage zu Zeile 14 im vorherigen Post. Wenn du die sinnentnehmend weiter auf die Zeile 7 in dem Parcel-Code anwendest, solltest du deine Lösung haben.



  • Da kommt der Fehler:
    error C2664: "Parcel::Parcel(int,float,Address &,bool,Address)" : Konvertierung von Argument 3 von "Address" in "Address &" nicht möglich
    Stimmt, das fällt ja unter den Punkt Const-Correctness. Wie meinst du das?



  • Stimmt, das fällt ja unter den Punkt Const-Correctness. Wie meinst du das?

    Genau so habe ich das gemeint! Du hast hiermit doch das Problem schon erkannt. Dann sollte "Problem gebannt" doch nur 1 Schritt entfernt sein! Ein temporäres Objekt kann halt nicht an eine non-const-Referenz binden (alle Änderungen an dem Objekt wären ja auch für die Katz).



  • Leider doch noch nicht ganz. Du hattest ja geschrieben dass ein temporäres Objekt nicht an eine non-const-Referenz gebunden werden kann. Warum ist das so...aus den Artikel: https://www.c-plusplus.net/forum/topic/39474/referenz-auf-ein-temporäres-objekt/12
    bin ich nicht schlau geworden. Und warum erzeugt Adress() ein temporäres Objekt und adr dann nicht. Beide befinden sich doch im Konstruktor der Klasse TraceParcel und sollte demzufolge über die Lebensdauer eines Objektes vom Typ TraceParcel existieren und demzufolge temporär sein? Danke!



  • Temporär bedeutet, daß ein Objekt direkt erzeugt wird, aber nach dem Aufruf nicht weiter verwendet wird (bzw. kann, weil es keiner Variablen zugewiesen ist).
    Und genau das ist bei Address() der Fall, während bei dem Parameter adr ja ein benanntes Objekt vorliegt (und der Compiler dann davon ausgeht, daß der Programmierer dies bewußt so erzeugt hat).

    Würde die Basisklasse Mail den Parameter ebenfalls als Referenz entgegennehmen und sogar in einer Referenz-Membervariablen halten, dann würde zwar auch kein Compilerfehler erfolgen, aber da die Lebenszeit des übergebenen Parameters adr kürzer als die Lebenszeit des Klassenobjekts wäre, dürfte danach (d.h. nach dem Konstruktor) nicht mehr über die gespeicherte Referenz darauf zugegriffen werden (UB).

    PS: Ich vermisse @HumeSikkins hier - er hat viel zum C++ Forum beigetragen und besonders auch seine eigene C++ Webseite (an deren genauen Namen ich mich nicht mehr erinnere: C++ FAQ oder s.ä.).



  • Aber sowohl Address() als auch adr werden doch den Referenzparameter t des Basisintialisierer zugewiesen und in den Fall verwendet? Trotzdem sind nicht beide temporäre Objekte. Irgendwie ist mir das noch nicht ganz klar.
    Nach Lesen des Beitrages von HumeSikkins muss man also einfach dass laut C++-Standard eine nicht konstante Referenz keinen temporären Objekt zugewiesen werden kann, ohne das zu hinterfragen?



  • Jetzt ist es klar...eigentlich habt ihr ja schon alles geschrieben. Nochmals vielen Dank!

    @Th69 : Zu Lebenszeiten von temporären Objekten die an eine Memberreferenz gebunden sind, habe ich einen
    weiteren guten Artikel gefunden. Darin steht:
    "
    Shlo 16. Aug. 2004, 01:43
    Laut dem Standard, bleibt eine temporäres Objekt, das an eine Referenz gebunden wird, bestehen bis die Referenz zerstört wird, außer - das temporäre Objekt wird in der Initialisierungsliste eines Konstruktors an eine Member-Referenz gebunden oder an einen Funktionsparameter. Dann endet die Lebendsdauer des Objekts mit dem Ablauf des Konstruktors bzw. der Funktion.
    "
    (https://www.c-plusplus.net/forum/topic/82903/lebensdauer-eines-temporären-objekts)

    Dies würde deiner Aussage von vor 4 Tagen ("...aber da die Lebenszeit des übergebenen Parameters adr kürzer als die Lebenszeit des Klassenobjekts wäre, dürfte danach (d.h. nach dem Konstruktor) nicht mehr über die gespeicherte Referenz darauf zugegriffen werden) doch eigentlich widersprechen?



  • Der Beispielcode aus dem Post ist wichtig. Das temporäre Objekt wird an eine const Referenz gebunden, dass ist in C++ erlaubt und verlängert die Lebenszeit des Objektes auf die Lebenszeit der const Referenz.



  • Ja genau...also ist die Aussage von vor 4 Tagen verkehrt



  • Nein, da in deinem Code

    TraceParcel(const Parcel& pc, Address adr): Parcel(1, 2, adr )
    

    adr als Wert übergeben wird (d.h. es wurde eine Kopie erzeugt).
    Reichst du diese als Referenz an die Basisklasse Parcel weiter (und wie schon geschrieben, würde diese Referenz dann gespeichert werden), so wird adr nach Verlassen des Konstruktor wieder zerstört und die gespeicherte Referenz referenziert ins Nirvana.

    Und selbst bei Address& adr wäre dies entsprechend so:

    ... außer - das temporäre Objekt wird in der Initialisierungsliste eines Konstruktors an eine Member-Referenz gebunden. Dann endet die Lebensdauer des Objekts mit dem Ablauf des Konstruktors ...


Anmelden zum Antworten