Menu
Coddy logo textTech

Polymorphie zur Compilezeit vs. Laufzeit

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy. Lektion 56 von 104.

Polymorphismus bedeutet „viele Formen“ und ist ein zentrales OOP-Konzept, das es ermöglicht, Objekte einheitlich zu behandeln, obwohl sie sich unterschiedlich verhalten. C++ unterstützt zwei verschiedene Arten von Polymorphismus, die jeweils in einer anderen Phase der Programmausführung aufgelöst werden.

Kompilierzeit-Polymorphie (auch statische Polymorphie genannt) wird vom Compiler aufgelöst, bevor das Programm ausgeführt wird. Der Compiler bestimmt anhand der Funktionssignatur genau, welche Funktion aufgerufen werden soll. Dazu gehören Funktionsüberladung und Templates:

void print(int x) { std::cout << "Integer: " << x << std::endl; }
void print(double x) { std::cout << "Double: " << x << std::endl; }

print(5);      // Compiler wählt print(int)
print(3.14);   // Compiler wählt print(double)

Laufzeitpolymorphismus (auch dynamischer Polymorphismus genannt) wird während der Ausführung des Programms aufgelöst. Die Entscheidung darüber, welche Funktion aufgerufen werden soll, hängt vom tatsächlichen Objekttyp ab, nicht vom Zeiger- oder Referenztyp. Dies wird durch virtuelle Funktionen erreicht:

class Shape {
public:
    virtual void draw() { std::cout << "Drawing shape" << std::endl; }
};

class Circle : public Shape {
public:
    void draw() override { std::cout << "Drawing circle" << std::endl; }
};

Shape* s = new Circle();
s->draw();  // Zur Laufzeit entschieden: "Drawing circle"

Der entscheidende Kompromiss: Polymorphie zur Kompilierzeit verursacht keinen Laufzeitaufwand, da Entscheidungen während der Kompilierung getroffen werden, während Polymorphie zur Laufzeit geringe Kosten (Nachschlagen in der vtable) verursacht, aber größere Flexibilität beim Arbeiten mit Objekten bietet, deren Typen erst bei der Ausführung bekannt sind.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Rechnersystem, das beide Arten von Polymorphismus direkt nebeneinander demonstriert. Du erstellst ein System, in dem der Polymorphismus zur Compile-Zeit verschiedene Eingabetypen durch Funktionsüberladung verarbeitet, während der Polymorphismus zur Laufzeit ermöglicht, verschiedene Berechnungsstrategien dynamisch auszutauschen.

Du organisierst deinen Code auf drei Dateien:

  • Calculator.h: Definiere eine Basisklasse Calculator, die jede Berechnungsstrategie repräsentiert:
    • Eine virtuelle Methode calculate(int a, int b), die ein int zurückgibt und Folgendes ausgibt: Base calculation: <a> ? <b> (wobei sie 0 zurückgibt)
    • Einen virtuellen Destruktor
  • Operations.h: Definiere zwei abgeleitete Rechnerklassen, die das Berechnungsverhalten überschreiben:
    • Adder: Überschreibe calculate(), sodass Adding: <a> + <b> ausgegeben und die Summe zurückgegeben wird
    • Multiplier: Überschreibe calculate(), sodass Multiplying: <a> * <b> ausgegeben und das Produkt zurückgegeben wird
    Beide Klassen sollten das Schlüsselwort override verwenden.
  • main.cpp: Erstelle ein System, das beide Polymorphismus-Arten vorführt. Lies zwei ganzzahlige Eingaben ein (jeweils in einer eigenen Zeile).

    Demonstriere zuerst den Polymorphismus zur Compile-Zeit, indem du drei überladene display()-Funktionen erstellst:

    • display(int x) gibt Folgendes aus: Integer value: <x>
    • display(double x) gibt Folgendes aus: Double value: <x>
    • display(const std::string& x) gibt Folgendes aus: String value: <x>

    Demonstriere anschließend den Polymorphismus zur Laufzeit, indem du ein Array aus Calculator*-Zeigern erstellst, das ein Basisobjekt Calculator, ein Adder-Objekt und ein Multiplier-Objekt enthält. Durchlaufe das Array und rufe für jedes Element mit deinen Eingabewerten calculate() auf. Gib nach jeder Berechnung das Ergebnis aus.

    Strukturiere deine Ausgabe wie folgt:

    === Compile-Time Polymorphism ===
    <display outputs for int, double, string>
    
    === Runtime Polymorphism ===
    <calculate outputs with results>

    Rufe im Abschnitt zur Compile-Zeit display() zuerst mit der ersten Eingabe als Ganzzahl auf, dann als Double (derselbe Wert mit hinzugefügtem .5) und anschließend als String "Result". Räume deine dynamisch angelegten Rechner am Ende auf.

Beispielsweise bei den Eingaben 10 und 3:

=== Compile-Time Polymorphism ===
Integer value: 10
Double value: 10.5
String value: Result

=== Runtime Polymorphism ===
Base calculation: 10 ? 3
Result: 0
Adding: 10 + 3
Result: 13
Multiplying: 10 * 3
Result: 30

Beachte, wie der Compiler die richtige display()-Überladung anhand des Argumenttyps auswählt (Entscheidung zur Compile-Zeit), während die richtige calculate()-Methode zur Laufzeit anhand des tatsächlichen Objekttyps über den VTable-Mechanismus bestimmt wird.

Probier es selbst

#include <iostream>
#include <string>
#include "Calculator.h"
#include "Operations.h"

// TODO: Erstelle drei überladene display()-Funktionen:
// 1. display(int x) - prints "Integer value: <x>"
// 2. display(double x) - gibt "Double value: <x>" aus
// 3. display(const std::string& x) - gibt "String value: <x>" aus



int main() {
    // Lese zwei Integer-Eingaben
    int a, b;
    std::cin >> a;
    std::cin >> b;
    
    // === Compile-Time Polymorphism ===
    std::cout << "=== Compile-Time Polymorphism ===" << std::endl;
    // TODO: Rufe display() auf mit:
    // - a als Integer
    // - a als Double (addiere 0.5 dazu)
    // - den String "Result"
    
    
    std::cout << std::endl;
    
    // === Runtime Polymorphism ===
    std::cout << "=== Runtime Polymorphism ===" << std::endl;
    // TODO: Erstelle ein Array von Calculator*-Zeigern mit 3 Elementen:
    // - a base Calculator
    // - einen Adder
    // - einen Multiplier
    
    // TODO: Durchlaufe das Array, rufe calculate(a, b) für jedes auf,
    // und gib "Result: <return_value>" nach jeder Berechnung aus
    
    
    // TODO: Dynamisch allokierten Speicher freigeben
    
    
    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-C++-Compiler