Es gibt viele Gründe, warum Compiler die Laufzeitentscheidung im Allgemeinen nicht durch statische Aufrufe ersetzen können, hauptsächlich weil es sich um Informationen handelt, die zur Kompilierungszeit nicht verfügbar sind, z. B. Konfiguration oder Benutzereingaben. Abgesehen davon möchte ich zwei weitere Gründe nennen, warum dies im Allgemeinen nicht möglich ist.
Erstens basiert das C ++ - Kompilierungsmodell auf separaten Kompilierungseinheiten. Wenn eine Einheit kompiliert wird, weiß der Compiler nur, was in den zu kompilierenden Quelldateien definiert ist. Stellen Sie sich eine Kompilierungseinheit mit einer Basisklasse und einer Funktion vor, die auf die Basisklasse verweist:
struct Base {
virtual void polymorphic() = 0;
};
void foo(Base& b) {b.polymorphic();}
Bei einer separaten Kompilierung kennt der Compiler die implementierten Typen Base
nicht und kann den dynamischen Versand daher nicht entfernen. Es ist auch nicht etwas, was wir wollen, weil wir das Programm durch Implementierung der Schnittstelle mit neuen Funktionen erweitern wollen. Möglicherweise ist dies zum Zeitpunkt der Verknüpfung möglich , jedoch nur unter der Annahme, dass das Programm vollständig abgeschlossen ist. Dynamische Bibliotheken können diese Annahme brechen, und wie im Folgenden zu sehen ist, wird es immer Fälle geben, in denen dies überhaupt nicht möglich ist.
Ein grundlegenderer Grund ist die Berechenbarkeitstheorie. Selbst mit vollständigen Informationen ist es nicht möglich, einen Algorithmus zu definieren, der berechnet, ob eine bestimmte Zeile in einem Programm erreicht wird oder nicht. Wenn Sie könnten, könnten Sie das Halteproblem lösen: Für ein Programm P
erstelle ich ein neues Programm, P'
indem ich am Ende eine zusätzliche Zeile hinzufüge P
. Der Algorithmus könnte nun entscheiden, ob diese Linie erreicht ist, wodurch das Halteproblem gelöst wird.
Im Allgemeinen nicht entscheiden zu können bedeutet, dass Compiler nicht entscheiden können, welcher Wert Variablen im Allgemeinen zugewiesen wird, z
bool someFunction( ) {
}
Base* b = nullptr;
if (someFunction( ... ))
b = new Derived1();
else
b = new Derived2();
b->polymorphicFunction();
Selbst wenn alle Parameter zur Kompilierungszeit bekannt sind, ist es nicht möglich, allgemein zu beweisen, welcher Pfad durch das Programm genommen wird und welchen statischen Typ b
haben wird. Annäherungen können und werden durch Optimierung von Compilern vorgenommen, aber es gibt immer Fälle, in denen dies nicht funktioniert.
Trotzdem bemühen sich C ++ - Compiler sehr, den dynamischen Versand zu entfernen, da dies viele andere Optimierungsmöglichkeiten eröffnet, vor allem durch die Möglichkeit, Wissen über den Code zu integrieren und zu verbreiten. Wenn Sie interessant sind, finden Sie eine Reihe interessanter Blog-Beiträge für die Implementierung der GCC- Devirtualisierung.