2016-05-30 3 views
1

Dieses Beispiel habe ich in Stroustrups "A Tour of C++" (2014) gesehen. Wenn Sie Funktionen mit den exakt gleichen Namen, aber unterschiedlichen Argumenttypen definieren, wählt der Compiler die am besten geeignete Funktion für jeden Anruf:Wie wird die Leistung des C++ - Compilers beeinflusst, indem mehrere Funktionen mit demselben Namen, aber unterschiedlichen Typen definiert werden?

void print(int);  // takes an integer argument 
void print(double); // takes a floating-point argument 
void print(string); // takes a string argument 

void user() 
{ 
    print(42);     // calls print(int) 
    print(9.65);    // calls print(double) 
    print("D is for digital"); // calls print(string) 
} 

(1) Hat die Benennung nicht Funktionen mit dem gleichen Namen wie dieses Ergebnis in einem (vielleicht kleiner) Performance-Hit?

(2) Wie genau wählt der Compiler bei der Eingabe die am besten geeignete Funktion aus? Was passiert hier hinter den Kulissen?

+0

Der Compiler betrachtet die Anzahl und Typen von Funktionsargumenten und wählt die Funktionsimplementierung aus, die dieser entspricht, oder wenn keine vorhanden ist [Beispiel avg (int1, int2, int3, int4), wobei keine definiert ist] gibt eine Fehlermeldung aus . –

+2

Wie für 1: Keine Laufzeitleistungsdifferenz für Ihr Programm. Und wahrscheinlich kein/unbedeutender Unterschied für die Kompilierzeit. Wie für 2: Es ist viel hinter den Kulissen da, also sei bereit für ein tiefes Lesen ... –

+0

Auch für 2: Wenn ich mich richtig erinnere erklärte STL es in seinem [Core C++] (https: // channel9) recht gut .msdn.com/Series/C9-Vorträge-Stephan-T-Lavavej-Core-C-/Core-Cpp-10) Videos (Link ist zum letzten Video in der Serie, da sie anscheinend keine Playlist-Funktion haben , die anderen Videos sind in der Beschreibung verlinkt). –

Antwort

2

Funktionsüberlastung (mindestens normal) hat keinen Einfluss auf die Ausführungsgeschwindigkeit. Überlegen Sie, ob Sie diese Funktionen wie schrieb:

void print_int(int);   // takes an integer argument 
void print_double(double); // takes a floating-point argument 
void print_string(string); // takes a string argument 

... und dann Dinge ausgedruckt von einem von ihnen die Wahl auf das, was Sie drucken wollten. Das ist ziemlich genau das, was der Compiler macht: Er nimmt die Anzahl und den Typ der Parameter und codiert sie in einen "entstellten" Namen. Wenn Sie einen Funktionsaufruf ausführen, durchsucht der Compiler (zur Kompilierungszeit) die verfügbaren Funktionen, wählt einen aus und erstellt einen Aufruf für diese Funktion. Der Code zum Aufrufen der Funktion ist identisch mit dem Code zum Aufrufen einer Funktion, die nicht überlastet wurde.

Die Auswahl der besten Funktion ist eine nicht-triviale Übung, um es milde auszudrücken. Es findet in einigen Phasen statt. Der erste besteht darin, einen Bereich zu finden, in dem etwas definiert mit dem Namen, den Sie verwendet haben. Die zweite besteht darin, alles in diesem Bereich mit diesem Namen zu durchsuchen, um eine Liste von Überladungen zu erstellen.

Der nächste Schritt ist die Beseitigung von Überlastungen, die überhaupt nicht funktionieren könnten - z. B. Überladungen, die die Anzahl der Argumente, die Sie übergeben haben, einfach nicht akzeptieren können. Wenn nur eine Funktion übrig bleibt, wird die Überladungsauflösung ausgeführt. Wenn es mehr als einen gibt, kommen wir zum letzten (und schwierigsten) Teil der Überladungsauflösung.

Der Compiler beginnt mit einer Liste möglicher impliziter Konvertierungen für jeden Typ und einer Rangfolge für jeden Typ. Das "Beste" ist die Identitätskonvertierung (d. H. Keine Konvertierung, z. B. wenn Sie eine int übergeben und die Funktion eine int erwartet). Etwas schlechter (aber immer noch ziemlich gut) ist etwas wie das Hinzufügen einer const. Irgendwann kommen Sie zu Dingen wie das Abschneiden eines Doppels zu einem int.

Der Compiler durchläuft dann nacheinander Argumente und untersucht die Konvertierung, die erforderlich ist, um von diesem Argument zum Typ für den formalen Parameter zu gelangen. Um als "beste" Überladung zu gelten und zur Verwendung ausgewählt zu werden, muss mindestens ein Argument eine bessere Konvertierung als die Konvertierung für jede andere Überladung haben, und keines der Argumente kann eine schlechtere als die Überlast haben würde für jede andere Überladung benötigt.

Wenn es so etwas nicht gibt (zB haben Sie nur zwei brauchbare Funktionen, und jede hat einen Parameter, der eine bessere Umwandlung hat, und ein Argument mit einer schlechteren Umwandlung), ist der Aufruf nicht eindeutig, so kann der Code nicht kompilieren.

+0

"Oh, schau mal", sagte ich, "Niemand hat diese Frage eine Stunde lang beantwortet, vielleicht, wenn ich etwas schreibe, werde ich nicht finden, dass jemand anderes es 30 Sekunden früher gründlich beantwortet hat." Fond hoffe! – Kundor

+0

@Kundor: Nun, wenn es irgendeinen Komfort gibt, scheint es zumindest, dass deine Schätzung von 30 Sekunden * genau * korrekt war! :-) –

1

(1) Es wird zur Laufzeit kein Performance-Treffer erzielt, da der Compiler herausfindet, welche Funktion aufgerufen und aufgerufen wird, genau wie jede andere Funktion.

(2) Name mangling. In Ihrem Beispiel, wenn Sie

void print(int);  // takes an integer argument 
void print(double); // takes a floating-point argument 
void print(string); // takes a string argument 

der Compiler sie verschiedene Namen gibt auf der Grundlage ihrer Parametertypen schreiben, vielleicht:

void print_i(int);  // takes an integer argument 
void print_d(double); // takes a floating-point argument 
void print_str(string); // takes a string argument 

Dann, wenn Sie print(42) nennen, sagt sie: „Oh, es mit genannt ein int, so nennen wir eigentlich print_i(42). "

+0

Danke. Können Sie erklären, was Sie meinen, "der Compiler gibt ihnen andere Namen"? – ShanZhengYang

+1

@ShanZhengYang: Aus praktischen Gründen hat jede C++ - Funktion einen internen eindeutigen Namen, der aus dem Funktionsnamen, den Klassen und/oder Namespaces, in denen er sich befindet, und den Funktionsargumenten abgeleitet ist. – MSalters

0

(1) Im Idealfall fallen keine zusätzlichen Kosten an. Wenn die Funktionen jedoch von einer gemeinsam genutzten Bibliothek exportiert werden, können beim Laden der Funktion zum ersten Mal höhere Laufzeitkosten aufgrund der Symbolauflösung entstehen. Dies hängt vollständig von der Implementierung des dynamischen Linkers ab. Wenn der dynamische Linker in einer exotischen Umgebung beispielsweise eine einfache Hash-Tabelle mit einer ungültigen Hash-Funktion verwendet (z. B. nur das Präfix eines Symbols berücksichtigt), können Hash-Kollisionen dazu führen, dass jede Suche O (n) -Icht nimmt.

Verwandte Themen