Einfache Frage zu Polymorphie in C++



  • Gut!
    Danke, verstanden soweit!


  • Administrator

    blub² schrieb:

    Jep. Intern gibt es mehrere überladene + operatoren die beispielsweise eine Addition von int mit double, float oder anderen möglich machen.

    Nein, das ist so nicht richtig. Tatsächlich wird vor der Operation eine Konvertierung durchgeführt. Das ganze ist definiert im Standard 2003, Kapitel "5 Expressions", Abschnitt 9:

    Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield
    result types in a similar way. The purpose is to yield a common type, which is also the type of the result.
    This pattern is called the usual arithmetic conversions, which are defined as follows:
    — If either operand is of type long double, the other shall be converted to long double.
    — Otherwise, if either operand is double, the other shall be converted to double.
    — Otherwise, if either operand is float, the other shall be converted to float.
    — Otherwise, the integral promotions (4.5) shall be performed on both operands.
    54)
    — Then, if either operand is unsigned long the other shall be converted to unsigned long.
    — Otherwise, if one operand is a long int and the other unsigned int, then if a long int can rep-
    resent all the values of an unsigned int, the unsigned int shall be converted to a long int;
    otherwise both operands shall be converted to unsigned long int.
    — Otherwise, if either operand is long, the other shall be converted to long.
    — Otherwise, if either operand is unsigned, the other shall be converted to unsigned.
    [Note: otherwise, the only remaining case is that both operands are int ]

    Grüssli



  • Autsch O.o
    Wie sehen denn dann die Datentypen intern aus?
    Ich hätte jetzt spontan gesagt das eine Konvertierung deutlich ineffektiver ist wie mehrere überladene Operatoren.



  • Trotzdem hab ich jetzt noch eine Frage, eine kurze!
    Was für Infos stehen mir zur Laufzeit zur Verfügung, die ich zur Übersetzungszeit noch nicht habe, die mir dann die auswahl einer richtigen methode ermöglichen, wenn es methoden gleicher signaturen sind.
    Also stichwort dynamische polymorphie!
    Hab schon einiges gelesen und immer stand, dass eben erst zur Laufzeit bekannt ist, mit welchen objekten die methode konkret belegt ist und ebenso erst zur Laufzeit bekannt ist, zu welcher klasse das objekt gehört ist.
    das macht dann möglich, dass das richtige ausgewählt wird bei der späten bindung!

    Aber was heißt das denn genau, verständlich erklärt?
    was ist erst zur laufzeit bekannt und wieso ist das so?
    und bitte keine seiten angeben!
    Ich finde das zwar sehr nett, aber ich brauche nur eine kurze verständliche erklärung über zwei drei sätze.
    überblicksmäßig!

    danke im voraus!



  • Ganz einfach? Gut man möchte das hier machen:

    Auto* base = 0;
    
    switch ( anwender_eingabe )
    {
      case 1: base = new opel; break;
      case 2: base = new audi; break;
      case 3: base = new ferrari; break;
      default: base = new vw;
    };
    
    base->fahre ();
    

    Hier kann man zur Kompilierzeit noch nicht wissen, für was sich der Anwender entscheidet, aber man weiss, dass das, wofür er sicht enscheidet fahren kann, also wird das von der Basisklasse unterstützt. (und unter Umständen von der abgeleiten Klasse überschrieben).


  • Administrator

    blub² schrieb:

    Wie sehen denn dann die Datentypen intern aus?

    Die Frage verstehe ich nicht so richtig. Was genau meinst du damit?

    blub² schrieb:

    Ich hätte jetzt spontan gesagt das eine Konvertierung deutlich ineffektiver ist wie mehrere überladene Operatoren.

    Um eine Konvertierung kommst du fast nicht rum. Oder was soll der Rückgabewert von einem Operator sein, wenn die beiden Parameter von unterschiedlichem Typ sind?

    @Morphologe,
    Ein kleines Beispiel:

    class Base
    {
    public:
      virtual ~Base() { };
    
      virtual void call() = 0;
    };
    
    class DerivedLeft
      : public Base
    {
    public:
      virtual void call() { std::cout << "Left" << std::endl; }
    };
    
    class DerivedRight
      : public Base
    {
    public:
      virtual void call() { std::cout << "Right" << std::endl; }
    };
    
    int main()
    {
      int i = 0;
      std::cin >> i;
    
      Base* b;
    
      if(i < 5)
      {
        b = new DerivedLeft();
      }
      else
      {
        b = new DerivedRight();
      }
    
      b->call(); // Erst zur Laufzeit ist bekannt, welche Methode aufgerufen wird,
      // also ob Left oder Right ausgegeben wird.
    
      // Edit: delete vergessen, ach die lieben Smart Pointern,
      // lassen mich schon verweichlichen :)
      delete b;
    
      return 0;
    }
    

    Die Auswahl geschieht zur Laufzeit dann über eine sogenannte VTable. In dieser wird nachgeschaut, ob die Methode call von DerivedLeft oder DerivedRight aufgerufen wird.

    Grüssli



  • Ah!

    Das heißt, da ich zb. selbst die funktion blabla erst aufrufe, natürlich zur laufzeit, oder eben selbst etwas einlese zur laufzeit, ist natürlich auch erst dann klar, welche objekte jetzt denn überhaupt die funktion aufrufen.
    und wenn man nicht weiß, welches objekt, welcher klasse, die funktion aufruft, kann man natürlich auch nicht die passende methode für dieses objekt auswählen!

    seh ich das richtig?



  • Ich verstehe blub²s Anmerkung schon. Er glaubt, vielleicht wäre es effizienter, eine Fliesskommazahl direkt mit einer Ganzzahl zu multiplizieren (als Beispiel). Der Rückgabetyp der Operation könnte dann trotzdem eine Fliesskommazahl sein.

    Nur weiss ich nicht, ob derartige Assemblerbefehle überhaupt existieren. Aber ehrlich gesagt kenne ich mich bei so maschinennahen Dingen zu wenig aus...



  • Um eine Konvertierung kommst du fast nicht rum. Oder was soll der Rückgabewert von einem Operator sein, wenn die beiden Parameter von unterschiedlichem Typ sind?

    Bis jetzt dachte ich der Typ des Left Hand Values...?

    Die Frage verstehe ich nicht so richtig. Was genau meinst du damit?

    Damit meine ich, wie man sich z.B. ein int intern vorstellen muss?
    Also, wie ist ein int intern aufgebaut, ich vermute mal das es keine Klasse ist.
    Oder eben, wenn ich int a; schreibe, was macht der Compiler dadraus?



  • Nexus schrieb:

    Ich verstehe blub²s Anmerkung schon. Er glaubt, vielleicht wäre es effizienter, eine Fliesskommazahl direkt mit einer Ganzzahl zu multiplizieren (als Beispiel). Der Rückgabetyp der Operation könnte dann trotzdem eine Fliesskommazahl sein.

    Nur weiss ich nicht, ob derartige Assemblerbefehle überhaupt existieren. Aber ehrlich gesagt kenne ich mich bei so maschinennahen Dingen zu wenig aus...

    Genau, das ist es. Hätte ich auch selber drauf kommen können ... 🙄
    Afaik geht eben das nämlich nicht womit klar wäre das man so oder so eine Konvertierung braucht.

    #edit: Hmmm.. je länger ich drüber nachdenke desto komischer kommt mir das wieder vor...

    #edit2: Zum internen aussehen eines Integers, kann es sein dass ein int nur eine Reihe von Bytes (im Normalfall eben 4) ist und sämtliche Arithmethische und sonstige Operationen mit Globalen Operatoren vollzogen werden?


  • Administrator

    @Nexus,
    Meine Kenntnisse sind da auch ziemlich bescheiden, aber dacht eigentlich, dass es nicht geht. Schon nur weil Gleitkommazahl-Operationen in einem anderen Bereich durchgeführt als integerale Operationen. Das ist doch die berühmte FPU.

    Auch mussten bisher bei allen Operationen, welche ich in Assembler geschrieben habe, die beiden Bytebreiten der Operanden gleichgross sein. Allerdings waren das bisher auch nicht so viele, welche ich geschrieben habe 🙂

    blub² schrieb:

    Bis jetzt dachte ich der Typ des Left Hand Values...?

    Eben nicht, zumindest in C++ 🙂

    blub² schrieb:

    Damit meine ich, wie man sich z.B. ein int intern vorstellen muss?
    Also, wie ist ein int intern aufgebaut, ich vermute mal das es keine Klasse ist.
    Oder eben, wenn ich int a; schreibe, was macht der Compiler dadraus?

    Für ein int a; würden wohl 4 Bytes auf einem 32 Bit System auf dem Stack angefordert werden. In welcher Reihenfolge ist Kompiler, bzw. Platformspezifisch -> Endian Problem

    Gleitkommazahlen werden nach IEEE 754 aufgebaut:
    http://de.wikipedia.org/wiki/IEEE_754

    Edit:
    @Morphologe,
    Fast hätte ich dich ganz vergessen 😃
    Deine Sätze sind zwar ein wenig kompliziert geschrieben, wenn ich sie aber richtig verstanden habe, dann ja. Allerdings musst nicht du die Funktion auswählen, sondern der Kompiler muss sie auswählen. Das kann er aber nicht, weil zur Kompilezeit nicht klar ist, worauf der Zeiger tatsächlich zeigt.

    Grüssli



  • Und der Zeiger weiß nicht worauf er zeigt, weil ihn zb. beeinfluss was ich einlese?


  • Administrator

    Morphologe schrieb:

    Und der Zeiger weiß nicht worauf er zeigt, weil ihn zb. beeinfluss was ich einlese?

    Auch hier wüsste er nicht, worauf er tatsächlich zeigt:

    int main()
    {
      Base* b = new DerivedLeft();
      b->call();
    
      return 0;
    }
    

    Dem Kompiler ist nur bekannt, dass es ein Zeiger auf ein Objekt vom Typ Base ist. Auf diesem Objekt wird nun die Methode call aufgerufen. Die Methode call ist virtuell, daher muss die VTable herangezogen werden, um die Methode aufzurufen. Die VTable wird beim Konstruieren des Objekts erstellt, was aber erst zur Laufzeit passiert.

    Verständlich? 🙂

    Grüssli



  • @Dravere

    Ich glaube du hast meinen zweiten Edit nicht mitbekommen (vermutlich gleichzeitg geschrieben :P):

    Zum internen aussehen eines Integers, kann es sein dass ein int nur eine Reihe von Bytes (im Normalfall eben 4) ist und sämtliche Arithmethische und sonstige Operationen mit Globalen Operatoren vollzogen werden?

    Denn ersten Teil hast du ja bereits beantwortet mit Teil zwei bin ich mir dann auch ziemlich sicher aber ist es auch wirklich so?


  • Administrator

    blub² schrieb:

    @Dravere

    Ich glaube du hast meinen zweiten Edit nicht mitbekommen (vermutlich gleichzeitg geschrieben :P):

    Zum internen aussehen eines Integers, kann es sein dass ein int nur eine Reihe von Bytes (im Normalfall eben 4) ist und sämtliche Arithmethische und sonstige Operationen mit Globalen Operatoren vollzogen werden?

    Denn ersten Teil hast du ja bereits beantwortet mit Teil zwei bin ich mir dann auch ziemlich sicher aber ist es auch wirklich so?

    Zum Teil 2:
    Ja. Wobei ich allerdings nicht ganz sicher bin, ob man sagen darf, dass die zugehörigen Operatoren global sind. Wahrscheinlich sind die nämlich nirgends definiert, sondern der Kompiler weiss einfach, was er da tun muss. Aber man könnte wohl sagen, dass sie sich wie globale Operatoren verhalten.

    Und Objekte sind einfach nur Bytemengen, welche nach gewissen Regeln im Speicher liegen. Fertig 😉
    Was hast du dir denn sonst darunter vorgestellt?

    Grüssli



  • Tja, gute Frage, eher so wie eine Klasse wobei das ja unlogisch ist. Man braucht ja elementare Datentypen auf denen alles andere aufbaut da kann man ja schlecht gleich mit Klassen als Basis anfangen.
    Auf jeden Fall vielen Dank, ist doch immer wieder schön wenn man wieder etwas von den Grundlagen verstanden hat.


Anmelden zum Antworten