Verständnisfrage zu abstrakter Klasse



  • Genau hier liegt mein Verständnisproblem:

    class Fahrzeug 
    { 
        virtual int getHP() const = 0;    // <- hat hier nichts zu suchen 
    }; 
    
    class Auto : public Fahrzeug
    {
     int getHP();   // hier macht die Methode Sinn
    }; 
    
    class Fahrrad : public Fahrzeug {};  // hier nicht, deshalb auch nicht in Fahrzeug
    

    Aber wenn ich jetzt eine Instanz Auto erzeugt habe, dann kann ich nur mittels dynamic_cast von Fahrzeug --> Auto auf getHP() zugreifen.
    Ist das dann ein Designfehler ?



  • Wie Michael E. bereits gesagt hat: Füg doch einfach eine Klasse MotorisiertesFahrzeug in die Hierarchie ein und verschieb die Methode in diese Klasse.



  • Auto auto_;
    std::cout << auto_.getPS();
    

    Ist absolut kein Problem. Schwierig wird es nur, wenn du eine Referenz/Zeiger auf ein Fahrzeug hast. Du weißt dann nicht, ob es überhaupt einen Antrieb hat, den man in PS messen kann. Folglich ist eine Abfrage fahrzeug->getPS() unmöglich.

    Schauen wir uns einmal folgenden Code an:

    void GibPsAus(Fahrzeug const& fahrzeug)
    {
        std::cout << dynamic_cast<Auto const&>(fahrzeug)->getPS();
    }
    

    Die Methode funktioniert nur, wenn der Parameter fahrzeug ein Auto ist. Also hätte man auch gleich als Parameter ein Auto angeben können. Verwendet man dynamic_cast, weil man weiß, dass eine Basisklassenreferenz einen bestimmten abgeleiteten Typ hat, hätte man auch direkt auf diesem Typ arbeiten können. Das ist z.B. eine unnötige Verwendung von dynamic_cast.

    Außerdem solltest du dynamic_cast nicht verwenden, um den Typen abzufragen. Dafür gibt es virtuelle Funktionen. Also statt:

    if (fahrrad = dynamic_cast<Fahrrad*>(fahrzeug))
        fahrrad->trete_in_die_pedale();
    else if (auto_ = dynamic_cast<Auto*>(fahrzeug))
        auto_->trete_das_gas_pedal();
    

    Lieber eine abstrakte virtuelle Methode:

    fahrzeug->beschleunige();
    

    Gibt sicherlich noch mehr Negativ-Beispiele. Ich habe dynamic_cast noch nie gebraucht. Wäre deshalb wohl interessiert an einem Positiv-Beispiel, in dem man dynmaic_cast benötigt.

    Gruß
    Don06



  • Trial schrieb:

    Aber wenn ich jetzt eine Instanz Auto erzeugt habe, dann kann ich nur mittels dynamic_cast von Fahrzeug --> Auto auf getHP() zugreifen.

    Nein, du brauchst den dynamic_cast nur, wenn du mit einem Basisklassenzeiger auf dieses Objekt zeigst. Das ist nur nötig, wenn du eine Sammlung von Fahrzeugen hast mit allen möglichen Fahrzeugtypen. Wenn du allerdings alle möglichen Fahrzeugtypen hast, macht eine Abfrage der PS-Zahl keinen Sinn.



  • Trial schrieb:

    Aber wenn ich jetzt eine Instanz Auto erzeugt habe, dann kann ich nur mittels dynamic_cast von Fahrzeug --> Auto auf getHP() zugreifen.

    Eben, hast du einen konkreten Anwendungsfall, bei dem du das musst und nicht direkt über die Auto -Klasse zugreifen kannst? Deshalb fragte ich ja vorhin. Gut möglich, dass wir dir Alternativen zeigen können.

    Don06 schrieb:

    Ich habe dynamic_cast noch nie gebraucht. Wäre deshalb wohl interessiert an einem Positiv-Beispiel, in dem man dynmaic_cast benötigt.

    Hier gabs mal so einen Thread: http://www.c-plusplus.net/forum/viewtopic-var-t-is-238923.html



  • Hi,

    mein Beispiel bezieht sich hier auf das Erstellen von Testdateien für verschiedene Drucker. In der Basisklasse sind die Eigenschaften wie Größe, Auflösung, Datenformat, Farbe etc. und in der jeweiligen Ableitung werden noch spezielle Daten für den ausgewählten Drucker aufbereitet.
    Hier habe ich meinen Ablauf etwas zusammengefaßt:

    // TestFile ist die Basisklasse
    // SPrinterIJ, LPrinterES und andere sind die Ableitungen
    
    TestFile *TF = 0;         // Deklaration und Initialisieren von TF (Basisklasse)
    
    ...
    
    // Hier wird abhängig von PrinterType das entsprechende Objekt erzeugt
    switch ( PrinterType )
    	  {
    	   case SPrinterIJ:
    						 TF = new SPrinterIJ();
    						 break;
    	   case LPrinterES:
    						 TF = new LPrinterES();
    						 break;
    ...
    
                default:
    						 break;
    	  }
    
    ...
    
    // Um die Funktion getJobParameter in der Ableitung SPrinterIJ anzusprechen muss ich den Zeiger casten
    SPrinterIJ *ptrPrinter = 0;
    ptrPrinter = dynamic_cast<SPrinterIJ*>(TF);
    if ( ptrPrinter != 0 )
      {
       ptrPrinter->getJobParameter( sJobParam );
      }
    


  • Also erstmal ist der Name der Basisklasse unglücklich. Denn ein Drucker ist kein File.

    Jeder Drucker hat Parameter. Also kannst du die Funktion getJobParameters() in die Basisklasse AbstractPrinter stecken.



  • Michael E. schrieb:

    Also erstmal ist der Name der Basisklasse unglücklich. Denn ein Drucker ist kein File.

    Den Namen habe ich gewählt weil das Endprodukt eine Testdatei ist.

    Michael E. schrieb:

    Jeder Drucker hat Parameter. Also kannst du die Funktion getJobParameters() in die Basisklasse AbstractPrinter stecken.

    Ja, das stimmt, aber in diesem Fall ist die Funktion für einen "speziellen" Job Header gedacht, der nur bei dem Model SPrinterIJ gebraucht wird. Alle anderen Druckerparameter werden über Methoden der Basisklasse definiert.
    Dann ist doch das downcasten in Ordnung, oder nicht ?!

    Ich gebe ja zu mit der Namensvergabe komm ich auch oft ins grübeln 🤡



  • Trial schrieb:

    Michael E. schrieb:

    Also erstmal ist der Name der Basisklasse unglücklich. Denn ein Drucker ist kein File.

    Den Namen habe ich gewählt weil das Endprodukt eine Testdatei ist.

    Und diese Testdatei ist Basisklasse deiner Drucker? Ich denke, du siehst den Designfehler hier. Denn ich mache Benzin auch nicht zur Basisklasse einer Tankstelle.

    Ja, das stimmt, aber in diesem Fall ist die Funktion für einen "speziellen" Job Header gedacht, der nur bei dem Model SPrinterIJ gebraucht wird. Alle anderen Druckerparameter werden über Methoden der Basisklasse definiert.
    Dann ist doch das downcasten in Ordnung, oder nicht ?!

    Für die zusätzlichen Parameter brauchst du aber keine neue Funktion.

    struct AbstractPrinter
    {
    	virtual void setParameters() = 0;
    
    private:
    	size_t breite_;
    	size_t hoehe_;
    };
    
    struct NormalPrinter : public AbstractPrinter
    {
    	virtual void setParameters(size_t breite, size_t hoehe)
    	{
    		breite_ = breite;
    		hoehe_ = hoehe;
    	}
    };
    
    struct SuperSpecialPrinter : public AbstractPrinter
    {
    	virtual void setParameters(size_t breite, size_t hoehe, int specialParameter)
    	{
    		breite_ = breite;
    		hoehe_ = hoehe;
    		specialParameter_ = specialParameter;
    	}
    
    private:
    	int specialParameter_;
    };
    


  • Hallo Michael,

    vielen Dank für dein Beispiel ! Jetzt ist mir das schon klarer geworden. 👍


Anmelden zum Antworten