Tail-Rekursion in C++
Kann mir jemand zeigen, eine einfache tail-rekursive Funktion in C++?
Warum ist tail recursion besser, wenn es gar ist?
Welche anderen Arten der Rekursion gibt es neben der tail recursion?
InformationsquelleAutor der Frage neuromancer | 2010-04-22
Du musst angemeldet sein, um einen Kommentar abzugeben.
Einem einfachen tail-rekursive Funktion:
Tail recursion ist im Grunde, wenn:
Und es ist nicht "besser", außer in dem Sinne, dass ein guter compiler kann das entfernen der Rekursion transformiert es in eine Schleife. Dies kann schneller sein, und wird sicherlich sparen auf stack-Nutzung. Der GCC-compiler kann dies tun-Optimierung.
InformationsquelleAutor der Antwort
Schwanz recusion in C++ genauso aussieht wie C oder einer anderen Sprache.
Tail-Rekursion (und tail-Aufruf im Allgemeinen) erfordert clearing den Aufrufer-stack-frame vor der Ausführung der tail-Aufruf. Für den Programmierer, tail recursion ist ähnlich wie ein loop, mit
return
reduziert zu arbeiten, wiegoto first_line;
. Der compiler braucht, um zu erkennen, was Sie tun, obwohl, und wenn es nicht ist, wird es noch eine zusätzliche stack-frame. Die meisten Compiler unterstützen es, aber das schreiben eine Schleife odergoto
ist in der Regel einfacher und weniger riskant.Nicht-tail-rekursive Aufrufe aktivieren können zufällige Verzweigung (wie
goto
zu der ersten Zeile von einer anderen Funktion), die eine einzigartige Einrichtung.Beachten Sie, dass in C++, es kann nicht sein, beliebige Objekte mit einem nicht trivialen Destruktor, der im Rahmen der
return
- Anweisung. Die end-of-cleanup-Funktion erfordern würde, die der angerufene zurück an den Aufrufer zurück, wodurch die Rute nennen.Beachten Sie auch (in jeder Sprache), die tail-Rekursion erfordert der gesamte Zustand des Algorithmus an, der durch die übergebene Funktion argument-Liste bei jedem Schritt. (Dies ergibt sich aus der Anforderung, dass die Funktion stack frame beseitigt werden, bevor der nächste Anruf beginnt... man kann nicht speichern alle Daten in lokalen Variablen.) Außerdem, keine operation, die angewendet werden können, um den Rückgabewert der Funktion, bevor es Schwanz zurück.
InformationsquelleAutor der Antwort Potatoswatter
Tail-Rekursion ist ein Spezialfall einer tail-call. Eine tail-call ist, wo der compiler sehen kann, dass es keine Operationen, die getan werden müssen, nach der Rückkehr von einer aufgerufenen Funktion, im wesentlichen drehen die aufgerufene Funktion die Rückkehr in seine eigene. Der compiler kann oft noch ein paar stack-Update-Operationen und dann springen (lieber als anrufen), um die Adresse der ersten Anweisung der aufgerufenen Funktion.
Einer der großen Dinge über diese neben der Ausschaltung einige Anrufe zurück ist, dass Sie auch reduzieren auf die stack-Nutzung. Auf manchen Plattformen im OS-code der stack kann Recht begrenzt zu sein und auf modernen Maschinen wie die x86-CPUs in unseren desktops Verringerung der stack-Nutzung, wie dies verbessert die Daten-cache-performance.
Tail recursion ist, wo die Funktion ist die gleiche wie die aufrufende Funktion. Dies kann sich in Schleifen, das ist genau das gleiche wie der Sprung in die tail-call-Optimierung die oben erwähnt werden. Denn dies ist die gleiche Funktion (angerufene und Anrufer) gibt es weniger stack-Korrekturen, die getan werden müssen, bevor Sie den Sprung.
Das folgende Beispiel zeigt eine Allgemeine Weise ein rekursiver Aufruf, die wäre schwieriger für einen compiler in eine Schleife:
Dies ist einfach genug, dass viele Compiler könnte wahrscheinlich heraus es sowieso, aber wie Sie sehen können gibt es eine Ergänzung, die passieren muss, um nach der Rückkehr von der genannten Summe eine Zahl zurück, so dass eine einfache tail-call-Optimierung ist nicht möglich.
Wenn Sie nicht:
Würden Sie in der Lage sein, um die Vorteile der Anrufe in beiden Funktionen sein Schwanz fordert. Hier die sum-Funktion die Hauptaufgabe ist das verschieben einer Wert-und klar, ein register oder stack-position. Die sum_helper hat alle von der math.
Da Sie erwähnten C++ in Ihrer Frage, die ich nennen werde, einige spezielle Dinge über, die.
C++ verbirgt einige Dinge von Ihnen, die C nicht. Diese Destruktoren sind die wichtigste Sache, die Sie erhalten, in die Art und Weise der tail-call-Optimierung.
In diesem Beispiel den Aufruf von baz nicht wirklich einen Schwanz nennen, weil z sein muss, zerstört nach der Rückkehr von baz. Ich glaube, dass die Regeln von C++ kann die Optimierung eher schwierig, selbst in Fällen, in denen die variable ist nicht notwendig für die Dauer des Gesprächs, wie zum Beispiel:
z möglicherweise zerstört nach der Rückkehr von qwerty-hier.
Eine andere Sache wäre, implizite Typ-Umwandlung, die passieren können in C, aber kann mehr kompliziert und Häufig in C++.
Zum Beispiel:
Hier Summe nennen zu sum_helper ist kein Schwanz nennen, weil sum_helper gibt ein double zurück und Summe benötigen, zu konvertieren, die in einer int.
In C++ ist es durchaus üblich, ein Objekt wieder Anhaltspunkte, die haben alle Arten von verschiedenen Interpretationen, von denen jede könnte eine andere Art der Konvertierung
Zum Beispiel:
Hier gibt es einen Aufruf cout.operator<< wie die Letzte Anweisung. cout gibt eine Referenz auf sich selbst (das ist, warum Sie können string viele Dinge, die zusammen in einer Liste, getrennt durch " << ), die Sie dann zwingen bewertet werden als bool, die endet Aufruf ein weiteres cout Methoden, operator bool(). Diese cout.operator bool() aufgerufen werden könnte, als einen tail-call in diesem Fall, aber operator<< konnte nicht.
EDIT:
Eine Sache, erwähnenswert ist, dass ein wesentlicher Grund, dass die tail-call-Optimierung in C möglich ist, ist, dass der compiler weiß, dass die aufgerufene Funktion speichern den Rückgabewert in der gleichen Stelle wie die aufrufende Funktion, um sicherzustellen, dass Ihr Rückgabewert wird gespeichert.
InformationsquelleAutor der Antwort nategoose
Tail-Rekursion existiert nicht wirklich auf compiler-Ebene in C++.Obwohl Sie schreiben Programme, verwenden Sie tail-Rekursion, die Sie nicht bekommen die Erben die Vorteile von tail-Rekursion implementiert, durch die Unterstützung von Compilern/Interpreten/Sprachen. Zum Beispiel-Schema unterstützt eine tail-Rekursion Optimierung, so dass es im Grunde ändern sich die Rekursion in iteration. Dies macht es schneller und unverwundbar zu stack-overflows. C++ keine so eine Sache. (zumindest nicht alle compiler, die ich gesehen habe)
Offenbar tail recursion Optimierungen gibt es in MSVC++ und GCC. Sehen diese Frage für details.
InformationsquelleAutor der Antwort Earlz
Wikipedia hat eine anständige Artikel über die tail-Rekursion. Im Grunde, tail recursion ist besser als reguläre Rekursion, denn es ist trivial zu optimieren, in einer iterativen Schleife, und iterative Schleifen sind in der Regel effizienter als rekursive Funktionen. Dies ist besonders wichtig bei funktionalen Sprachen, in denen Sie nicht Schleifen.
Für C++, ist es doch gut, wenn Sie können schreiben Sie Ihre rekursive loops mit tail-Rekursion, da Sie besser optimiert, aber in solchen Fällen, können Sie in der Regel tun Sie es einfach iterativ in Erster Linie, so dass der Gewinn ist nicht so groß, wie es sein würde, in einer funktionalen Sprache.
InformationsquelleAutor der Antwort Jonathan M Davis
Tail recursion ist ein trick, um tatsächlich zu bewältigen mit zwei Fragen zur gleichen Zeit. Die erste ist die Ausführung einer Schleife, wenn es schwierig ist, zu wissen, die Anzahl der Iterationen zu tun.
Aber dies kann erarbeitet werden mit einfacher Rekursion, das zweite problem entsteht, ist, dass der stack-überlauf aufgrund der rekursive Aufruf wird ausgeführt, zu viele Male. Die tail-call ist die Lösung, wenn Sie begleitet werden durch eine "compute-and carry" - Technik.
In basic CS, die Sie lernen, dass ein computer-Algorithmus braucht, um eine invariante und eine Abbruchbedingung. Dies ist die Basis für den Aufbau der tail-Rekursion.
Um es einfach auszudrücken, keine Berechnung geschehen muss, auf der Rückgabewert deiner Funktion .
Nehmen Sie zum Beispiel die Berechnung einer Potenz von 10, das ist trivial und kann geschrieben werden, indem eine Schleife.
Sollte so Aussehen wie
Dieser gibt eine Ausführung, e.g 4:
ret,p,res
Es ist klar, dass der compiler nur Werte kopieren ohne änderung der stack-pointer und wenn der Schwanz nennen, geschieht nur das Ergebnis zurückgeben.
Tail recursion ist sehr wichtig, weil er das fertige Kompilierung Auswertungen, z.B. Die oben gemacht werden kann.
dies kann verwendet werden, als
powc10<10>()()
zur Berechnung der 10 macht bei der Kompilierung.Meisten Compiler haben eine Begrenzung der Verschachtelung, also die tail-call-trick hilft. Offenbar gibt es keine meta-Programmierung-Schleifen, so haben, Rekursion zu verwenden.
InformationsquelleAutor der Antwort g24l