Menu
Coddy logo textTech

Virtuelle Funktionen – Wiederholung

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

Nachdem wir nun den Unterschied zwischen Polymorphismus zur Kompilierzeit und zur Laufzeit verstehen, werfen wir einen tieferen Blick auf virtuelle Funktionen und das Schlüsselwort override, das ihre Verwendung sicherer macht.

Wenn Sie eine Funktion in einer Basisklasse als virtual markieren, können abgeleitete Klassen ihre eigene Implementierung bereitstellen. Der Bezeichner override teilt dem Compiler explizit mit, dass Sie beabsichtigen, eine virtuelle Funktion zu überschreiben:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Die Verwendung von override ist entscheidend, da sie Fehler zur Kompilierzeit abfängt. Wenn Sie versehentlich den Funktionsnamen falsch schreiben oder falsche Parameter verwenden, wird der Compiler Sie warnen, anstatt stillschweigend eine neue Funktion zu erstellen:

class Cat : public Animal {
public:
    void speek() override {  // Kompilierfehler: keine Funktion zum Überschreiben
        std::cout << "Meow!" << std::endl;
    }
};

Der final-Bezeichner verhindert weiteres Überschreiben. Verwenden Sie ihn, wenn eine abgeleitete Klasse die letzte sein soll, die eine bestimmte Funktion überschreibt:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // Fehler: Eine finale Funktion kann nicht überschrieben werden
};

Verwenden Sie immer override beim Überschreiben virtueller Funktionen. Es dokumentiert Ihre Absicht und lässt den Compiler überprüfen, dass Sie tatsächlich eine bestehende virtuelle Funktion überschreiben.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Benachrichtigungssystem bauen, das die Leistungsfähigkeit der Schlüsselwörter override und final bei virtuellen Funktionen demonstriert. Sie werden eine Hierarchie von Benachrichtigungs-Handlern erstellen, bei denen einige Methoden weiter angepasst werden können und andere gesperrt sind, um Änderungen zu verhindern.

Sie werden Ihren Code über drei Dateien organisieren:

  • Notifier.h: Definieren Sie eine Basisklasse Notifier, die einen beliebigen Benachrichtigungs-Sender repräsentiert:
    • Ein geschütztes (protected) std::string recipient Mitglied
    • Ein Konstruktor, der den Namen des Empfängers entgegennimmt und speichert
    • Eine virtuelle send(const std::string& message) Methode, die Folgendes ausgibt: Notifying <recipient>: <message>
    • Eine virtuelle getType() Methode, die den String "Generic" zurückgibt
    • Ein virtueller Destruktor
  • EmailNotifier.h: Definieren Sie eine Klasse EmailNotifier, die von Notifier erbt:
    • Ein privates std::string domain Mitglied
    • Ein Konstruktor, der Empfänger und Domain entgegennimmt und den Empfänger an die Basisklasse übergibt
    • Überschreiben Sie send(), um Folgendes auszugeben: Emailing <recipient>@<domain>: <message>
    • Überschreiben Sie getType() und markieren Sie es als final — es sollte "Email" zurückgeben

    Definieren Sie dann eine Klasse UrgentEmailNotifier, die von EmailNotifier erbt:

    • Ein Konstruktor, der Empfänger und Domain entgegennimmt und beides an EmailNotifier übergibt
    • Überschreiben Sie send(), um Folgendes auszugeben: [URGENT] Emailing <recipient>@<domain>: <message>
    • Hinweis: Sie können getType() hier nicht überschreiben, da es in EmailNotifier als final markiert wurde
  • main.cpp: Lesen Sie drei Eingaben ein (jede in einer separaten Zeile):
    1. Name des Empfängers
    2. E-Mail-Domain
    3. Nachrichtentext

    Erstellen Sie drei Benachrichtigungsobjekte dynamisch: einen Basis-Notifier, einen EmailNotifier und einen UrgentEmailNotifier — alle unter Verwendung desselben Empfängers (und der Domain, wo zutreffend). Speichern Sie diese in einem Array von Notifier*-Pointern.

    Iterieren Sie durch das Array und geben Sie für jeden Notifier seinen Typ mit getType() aus, und rufen Sie dann send() mit Ihrer Nachricht auf. Formatieren Sie jeden Eintrag wie folgt:

    Type: <type>
    <send output>

    Geben Sie eine Leerzeile zwischen den einzelnen Notifiern aus. Bereinigen Sie Ihre dynamisch zugewiesenen Objekte, wenn Sie fertig sind.

Zum Beispiel mit den Eingaben Alice, company.com und Meeting at 3pm:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

Beachten Sie, wie UrgentEmailNotifier die Methode send() überschreiben kann, um das Nachrichtenformat anzupassen, aber den Typ "Email" von EmailNotifier erbt, weil getType() als final markiert wurde. Verwenden Sie das Schlüsselwort override bei allen überschriebenen Methoden, um etwaige Signatur-Fehler bereits zur Kompilierzeit zu erkennen.

Spickzettel

Der Bezeichner override teilt dem Compiler explizit mit, dass Sie beabsichtigen, eine virtuelle Funktion aus einer Basisklasse zu überschreiben:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Die Verwendung von override fängt Fehler zur Kompilierzeit ab. Wenn Sie den Funktionsnamen falsch schreiben oder falsche Parameter verwenden, wird der Compiler Sie warnen:

class Cat : public Animal {
public:
    void speek() override {  // Compiler-Fehler: keine Funktion zum Überschreiben
        std::cout << "Meow!" << std::endl;
    }
};

Der Bezeichner final verhindert weiteres Überschreiben in abgeleiteten Klassen:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // Fehler: finale Funktion kann nicht überschrieben werden
};

Verwenden Sie beim Überschreiben virtueller Funktionen immer override, um Ihre Absicht zu dokumentieren und die Überprüfung durch den Compiler zu ermöglichen.

Probier es selbst

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

int main() {
    // Eingaben lesen
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: Erstelle ein Array aus Notifier* Pointern mit 3 Elementen
    
    // TODO: Dynamisch erstellen:
    // - Einen Basis-Notifier mit dem recipient
    // - Einen EmailNotifier mit recipient und domain
    // - Einen UrgentEmailNotifier mit recipient und domain
    
    // TODO: Durchlaufe das Array und für jeden Notifier:
    // - Gib "Type: " gefolgt vom Ergebnis von getType() aus
    // - Rufe send() mit der message auf
    // - Gib eine Leerzeile zwischen den Notifiers aus (nicht nach dem letzten)
    
    // TODO: Dynamisch zugewiesene Objekte bereinigen
    
    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