ProgrammierungC++ System-/Infrastrukturentwickler

Erzählen Sie von der Reihenfolge der Suche nach Funktionen und Variablen bei der Vererbung (Name Lookup, Scope) sowie von den Schwierigkeiten, die bei der Mehrdeutigkeit von Namen in Hierarchien (insbesondere bei virtueller Vererbung) auftreten. Wie löst man solche Konflikte?

Bestehen Sie Vorstellungsgespräche mit dem Hintsage-KI-Assistenten

Antwort

In C++ wird die Reihenfolge der Namenssuche durch die Regeln der Namensauflösung (Name Lookup) geregelt, die die Gültigkeitsbereiche (Scope), das Verstecken (Hiding) und die Besonderheiten der Vererbung berücksichtigen. Wichtige Punkte:

  • Bei der Methode/Variablenaufruf sucht man von dem abgeleiteten Bereich zur Basis.
  • Bei Mehrfachvererbung, wenn ein Name in beiden Basisklassen vorhanden ist, tritt Mehrdeutigkeit (Ambiguity) auf.
  • Virtuelle Vererbung hilft, Duplikate (Diamond Problem) zu vermeiden, erfordert jedoch ebenfalls eine explizite Handhabung von Namen.

Konfliktlösung:

  • Für Funktionen, die im abgeleiteten Klassenschema versteckt sind, kann man using verwenden:
class Base { public: void foo(int) {} }; class Derived : public Base { public: using Base::foo; void foo(double) {} // Überladene Version };
  • Für Variablen oder Funktionen mit dem gleichen Namen:
    • Es ist notwendig, den Namen der Basisgruppe explizit anzugeben: Base::x oder Base::foo().

Beispiel für Mehrdeutigkeit bei Diamond Inheritance:

struct A { int x; }; struct B : virtual A {}; struct C : virtual A {}; struct D : B, C { void test() { x = 42; } // OK: x ist einmalig, dank virtual };

Fangfrage

Wenn in der Basisklasse eine Methode foo(int) deklariert ist und in der abgeleiteten Klasse foo(double), ist foo(int) aus einem Objekt der abgeleiteten Klasse zugänglich?

Antwort: Nein, die Methode foo(double) "versteckt" alle überladenen Versionen von foo in der Basisklasse. Um auf foo(int) zuzugreifen, muss man explizit using Base::foo oder Base::foo im Aufruf schreiben.

Beispiel:

class Base { public: void foo(int) { } }; class Derived : public Base { public: void foo(double) {} }; Derived d; d.foo(3); // Fehler! foo(int) ist nicht sichtbar

Verwenden wir:

class Derived : public Base { public: using Base::foo; void foo(double) {} }; d.foo(3); // Alles funktioniert

Beispiele für reale Fehler wegen Unwissenheit über die Feinheiten des Themas


Geschichte

In einem Projekt mit Mehrfachvererbung erschien eine Klasse, die von zwei Basisklassen mit gleichen Methoden foo() abgeleitet war. Die Autoren verwendeten keine virtuelle Vererbung, es wurden zwei Kopien der Basisklasse erstellt — alle Aufrufe an foo() wurden mehrdeutig, der Compiler weigerte sich, den Code zu kompilieren. Es wurde durch die Anwendung von virtueller Vererbung und die explizite Angabe von Base::foo() beim Aufruf gelöst.


Geschichte

In einer Grafik-Engine definierte eine abgeleitete Klasse ihre eigene draw(), ohne using für draw() der Basisklasse zu machen. Bei der Umgestaltung des Codes (Refactor) wurde nicht die richtige Version von draw() aufgerufen — Teile der Schnittstelle hörten auf, gerendert zu werden. Der Fehler wurde erst nach einer tiefgehenden Analyse der Klassenstruktur gefunden.


Geschichte

Bei der Umstellung auf einen neuen Compiler wurde die Kompilierung aufgrund der Mehrdeutigkeit von Namen bei Mehrfach- und virtueller Vererbung unmöglich. Die zuvor funktionierende Implementierung berücksichtigte keine unterschiedlichen Include-Ketten, die zu unterschiedlichen Namen in der Übersetzungseinheit führten.