Dynamische Objekte



  • Necrotos schrieb:

    Also ohne virtual werden die Funktionen aus A aufgerufen, weil es als Objekt vom Typ A gehandhabt wird, aber mit virtual wird dem Objekt mitgeteilt, dass es eigentlich doch von B ist, was ihm vorher nicht bekannt war, und dann werden eben die Funktionen aus B aufgerufen?

    Klassen mit virtuellen Funktionen bekommen in der Regel (siehe SeppJ's Kommentar dazu) eine Struktur zugewiesen, in der Überschreibungen für diese virtuellen Funktionen drinstehen. In diese Struktur schaut der Compiler rein, wenn du b2->print2() machst. Weil print2 überschrieben wurde, ruft er also die Funktion auf, die er an der Stelle findet - und das ist nun mal B::print2 .

    print1() hat so eine Überschreibung nicht bekommen, es existiert kein Eintrag in der vtable . Deswegen ruft der Compiler, weil das Objekt ein A ist, halt auch A::print1 auf.

    Ich glaube, damit bleiben keine Fragen offen.

    EDIT: C# ist (zumindest, als ich die Sprache gelernt habe) ein bisschen deutlicher in der Namensgebung. Funktionen in Basisklassen werden als virtual deklariert, Funktionen in abgeleiteten Klassen mit override (weil die Funktionen halt überschrieben werden). In C++ gibt man nur virtual in der Basisklasse an - das virtual in der abgeleiteten Klasse ist optional.



  • dachschaden schrieb:

    EDIT: C# ist (zumindest, als ich die Sprache gelernt habe) ein bisschen deutlicher in der Namensgebung. Funktionen in Basisklassen werden als virtual deklariert, Funktionen in abgeleiteten Klassen mit override (weil die Funktionen halt überschrieben werden). In C++ gibt man nur virtual in der Basisklasse an - das virtual in der abgeleiteten Klasse ist optional.

    Aber auch nur vor C++11. Mit C++11 gibt es das override keyword.
    Damit kann man in einer abgeleiteten Klasse eine methode als "überschreibt die gleiche methode der basis klasse" markieren.
    Wenn der compiler auf dieses keyword stößt so kann er überprüfen ob in der Basis klasse wirklich die gleiche Methode existert und als virtual markiert ist.
    Und im Fehlerfall wird eine Warnung/Fehlermeldung beim Übersetzen generiert.



  • In diese Struktur schaut der Compiler rein, wenn du b2->print2() machst.

    Also um das mit meinen eigenen Worten zu formulieren: Bei der Funktion ohne virtual wird die Funktion aus A ausgeführt, weil b2 ein Zeiger vom Typ A ist.
    Mit virtual wird nachgeschaut welche der beiden Funktionen ausgeführt werden soll. in dem Fall hier wird dann die Funktion aus B ausgeführt, weil da ein von A abgeleitetes Objekt ist (begingt durch new B() ).

    Entschuldigt bitte wenn die Formulierung nicht einwandfrei ist, ich hab mit C++ erst vor kurzem angefangen, deswegen bin ich mir auf einigen Gebieten noch nicht wirklich sicher.



  • Necrotos schrieb:

    Also um das mit meinen eigenen Worten zu formulieren: Bei der Funktion ohne virtual wird die Funktion aus A ausgeführt, weil b2 ein Zeiger vom Typ A ist.
    Mit virtual wird nachgeschaut welche der beiden Funktionen ausgeführt werden soll.

    Jein.
    Wenn auch nur eine Funktion in deiner Klasse virtual markiert wurde, wird die vtable oder was auch immer angelegt. Und dann wird immer da reingeschaut bei Funktionsaufrufen. Und wenn eine Funktion nicht drin ist, weil da halt nur virtuelle Funktionen reinkommen, dann wird die Basisklassenfunktion aufgerufen, wie sonst halt auch. Aber sobald eine überschriebene Funktion da gefunden wurde, wird die aufgerufen.

    Vom Endergebnis ist die Erklärung die gleiche wie deine Zusammenfassung, aber die Implementierung ist unterschiedlich.



  • Bei jedem Funktionsaufruf wird also nachgeschaut ob die Funktion überschrieben wurde. Wenn eine überschriebene Funktion gefunden wird, wird die auch aufgerufen.

    Diese Formulierung verwirrt mich leicht, das klingt so als ob die virtuelle Funktion immer aufgerufen wird, unabhängig davon ob das Objekt zu A oder B gehört.



  • Necrotos schrieb:

    Bei jedem Funktionsaufruf wird also nachgeschaut ob die Funktion überschrieben wurde. Wenn eine überschriebene Funktion gefunden wird, wird die auch aufgerufen.

    Muss halt, nicht?
    Früher (was heißt früher; so macht man das auch noch in C) hat man Funktionszeiger verwendet, wenn man zur Laufzeit bestimmen wollte, welche Funktion aufgerufen wird. Und Interpretercode, bei dem es eine Menge Instruktionen gibt, macht man durch Branch Prediction, was CPUs heutzutage unterstützen, richtig lahm, deswegen macht man dann sowas branchless. Und dann kommen Funktionszeiger dran.

    Und das Problem ist ja, dass der Compiler nicht weiß, was für ein Objekt da jetzt hintersteckt. Du kannst ja deine Basisklasse noch ganz woanders ableiten lassen und Objekte davon in einen Zeiger auf die Basisklasse packen. Deswegen muss bei Funktionsaufrufen nachgeschaut werden, welche Funktion aufgerufen wird. Das sind dann die Funktionszeiger, die du so gar nicht mehr siehst, aber sie sind da.

    Necrotos schrieb:

    Diese Formulierung verwirrt mich leicht, das klingt so als ob die virtuelle Funktion immer aufgerufen wird, unabhängig davon ob das Objekt zu A oder B gehört.

    Nee, nee. In die Tabelle/Struktur/Whatever wird immer reingeschaut. Was gefunden wird, wird aufgerufen. Und wenn's nur der Default ist, auch bekannt als die Funktion der Basisklasse.



  • Necrotos schrieb:

    Bei jedem Funktionsaufruf wird also nachgeschaut ob die Funktion überschrieben wurde.

    Nein. Zumindest nicht so, wie ich den Satz verstehen würde. Jedes Objekt hat einen Zeiger auf eine komplette vtable. Der Compiler kümmert sich zur Kompilierzeit darum. Wenn die Klasse eine Funktion der Basisklasse überschreibt, kommt an der entsprechenden Stelle in der Tabelle ein Zeiger auf die Funktion der abgeleiteten Klasse, ansonsten ein Zeiger auf die Funktion der Basisklasse.
    Es ist auf jeden Fall nur ein Funktionszeiger, zur Laufzeit muss man so gesehen nichts "nachschauen".



  • Vielen Dank Leute! Die Frage hat viel weiter geführt als gedacht.



  • ---> Unsinn.



  • Gut, vielleicht versteh ichs nicht unbedingt. Aber ich hab jetzt eine grobe Ahnung was im Hintergrund passiert. Sehr grob.


Anmelden zum Antworten