Dies ist keine Frage zur Lambda-Funktion. Ich weiß, dass ich einer Variablen ein Lambda zuweisen kann.
Was bringt es uns, eine Funktion im Code zu deklarieren, aber nicht zu definieren?
Zum Beispiel:
#include <iostream>
int main()
{
// This is illegal
// int one(int bar) { return 13 + bar; }
// This is legal, but why would I want this?
int two(int bar);
// This gets the job done but man it's complicated
class three{
int m_iBar;
public:
three(int bar):m_iBar(13 + bar){}
operator int(){return m_iBar;}
};
std::cout << three(42) << '\n';
return 0;
}
Ich möchte also wissen, warum C ++ zulassen sollte, two
was nutzlos und three
weitaus komplizierter erscheint, aber nicht erlaubt one
.
BEARBEITEN:
Aus den Antworten geht hervor, dass die In-Code-Deklaration möglicherweise die Verschmutzung von Namespaces verhindern kann. Ich hatte jedoch gehofft zu hören, warum die Möglichkeit, Funktionen zu deklarieren, zulässig war, die Möglichkeit, Funktionen zu definieren, jedoch nicht zulässig war.
c++
functor
function-declaration
Jonathan Mee
quelle
quelle
one
eine Funktion ist , Definition , sind die anderen zwei Erklärungen .Antworten:
Es ist nicht offensichtlich, warum dies
one
nicht erlaubt ist. verschachtelte Funktionen wurden vor langer Zeit in N0295 vorgeschlagen, die besagt:Natürlich wurde dieser Vorschlag abgelehnt, aber da wir online keine Sitzungsprotokolle zur Verfügung
1993
haben, haben wir keine mögliche Quelle für die Gründe für diese Ablehnung.Tatsächlich wird dieser Vorschlag in Lambda-Ausdrücken und -Verschlüssen für C ++ als mögliche Alternative erwähnt:
In Anbetracht der Tatsache, dass wir jetzt Lambdas haben, ist es unwahrscheinlich, dass verschachtelte Funktionen angezeigt werden, da sie, wie in der Veröffentlichung beschrieben, Alternativen für dasselbe Problem darstellen und verschachtelte Funktionen im Vergleich zu Lambdas mehrere Einschränkungen aufweisen.
Was diesen Teil Ihrer Frage betrifft:
Es gibt Fälle, in denen dies eine nützliche Möglichkeit ist, die gewünschte Funktion aufzurufen. Der Entwurf des C ++ - Standardabschnitts
3.4.1
[basic.lookup.unqual] gibt uns ein interessantes Beispiel:namespace NS { class T { }; void f(T); void g(T, int); } NS::T parm; void g(NS::T, float); int main() { f(parm); // OK: calls NS::f extern void g(NS::T, float); g(parm, 1); // OK: calls g(NS::T, float) }
quelle
::g(parm, 1)
, um die Funktion im globalen Namespace aufzurufen? Oder anrufen,g(parm, 1.0f);
was zu einer besseren Übereinstimmung mit dem gewünschten Ergebnis führen sollteg
?Die Antwort lautet "historische Gründe". In C könnten Funktionsdeklarationen im Blockbereich vorhanden sein, und die C ++ - Designer sahen keinen Vorteil darin, diese Option zu entfernen.
Eine beispielhafte Verwendung wäre:
#include <iostream> int main() { int func(); func(); } int func() { std::cout << "Hello\n"; }
IMO ist dies eine schlechte Idee, da es leicht ist, einen Fehler zu machen, indem eine Deklaration angegeben wird, die nicht der tatsächlichen Definition der Funktion entspricht, was zu undefiniertem Verhalten führt, das vom Compiler nicht diagnostiziert wird.
quelle
some_type f();
und eine Definition in einer anderen Übersetzungseinheitanother_type f() {...}
. Der Compiler kann Ihnen nicht sagen, dass diese nicht übereinstimmen, und ein Aufruff
mit der falschen Deklaration führt zu undefiniertem Verhalten. Es ist daher eine gute Idee, nur eine Deklaration in einem Header zu haben und diesen Header einzuschließen, in dem die Funktion definiert ist und in dem sie verwendet wird.In dem von Ihnen angegebenen Beispiel
void two(int)
wird es als externe Funktion deklariert, wobei diese Deklaration nur im Rahmen dermain
Funktion gültig ist .Dies ist sinnvoll, wenn Sie den Namen
two
nur innerhalb verfügbar machen möchten, ummain()
eine Verschmutzung des globalen Namespace in der aktuellen Kompilierungseinheit zu vermeiden.Beispiel als Antwort auf Kommentare:
main.cpp:
int main() { int foo(); return foo(); }
foo.cpp:
int foo() { return 0; }
Keine Notwendigkeit für Header-Dateien. kompilieren und verknüpfen mit
Es wird kompiliert und ausgeführt, und das Programm gibt wie erwartet 0 zurück.
quelle
two
auch in der Datei definiert werden, wodurch die Verschmutzung trotzdem verursacht wird?two()
könnte in einer völlig anderen Kompilierungseinheit definiert werden.Sie können diese Dinge tun, vor allem, weil sie eigentlich gar nicht so schwer zu tun sind.
Aus Sicht des Compilers ist die Implementierung einer Funktionsdeklaration in einer anderen Funktion ziemlich trivial. Der Compiler benötigt einen Mechanismus, mit dem Deklarationen innerhalb von
int x;
Funktionen ohnehin andere Deklarationen (z. B. ) innerhalb einer Funktion verarbeiten können.Es verfügt normalerweise über einen allgemeinen Mechanismus zum Parsen einer Deklaration. Für den Typ, der den Compiler schreibt, ist es überhaupt nicht wichtig, ob dieser Mechanismus beim Parsen von Code innerhalb oder außerhalb einer anderen Funktion aufgerufen wird - es ist nur eine Deklaration. Wenn er also genug sieht, um zu wissen, dass es eine Deklaration gibt, Es ruft den Teil des Compilers auf, der sich mit Deklarationen befasst.
Tatsächlich würde das Verbot dieser bestimmten Deklarationen innerhalb einer Funktion wahrscheinlich zu einer zusätzlichen Komplexität führen, da der Compiler dann eine völlig kostenlose Überprüfung benötigen würde, um festzustellen, ob er bereits Code in einer Funktionsdefinition betrachtet, und auf dieser Grundlage entscheiden, ob diese bestimmte Deklaration zugelassen oder verboten werden soll Erklärung.
Damit bleibt die Frage, wie sich eine verschachtelte Funktion unterscheidet. Eine verschachtelte Funktion unterscheidet sich dadurch, wie sie sich auf die Codegenerierung auswirkt. In Sprachen, die verschachtelte Funktionen zulassen (z. B. Pascal), erwarten Sie normalerweise, dass der Code in der verschachtelten Funktion direkten Zugriff auf die Variablen der Funktion hat, in der er verschachtelt ist. Zum Beispiel:
int foo() { int x; int bar() { x = 1; // Should assign to the `x` defined in `foo`. } }
Ohne lokale Funktionen ist der Code für den Zugriff auf lokale Variablen ziemlich einfach. In einer typischen Implementierung wird beim Eintritt der Ausführung in die Funktion ein Speicherplatzblock für lokale Variablen auf dem Stapel zugewiesen. Alle lokalen Variablen werden in diesem einzelnen Block zugewiesen, und jede Variable wird einfach als Versatz vom Anfang (oder Ende) des Blocks behandelt. Betrachten wir zum Beispiel eine Funktion wie diese:
int f() { int x; int y; x = 1; y = x; return y; }
Ein Compiler (vorausgesetzt, er hat den zusätzlichen Code nicht optimiert) generiert möglicherweise Code für diesen ungefähr äquivalenten Code:
stack_pointer -= 2 * sizeof(int); // allocate space for local variables x_offset = 0; y_offset = sizeof(int); stack_pointer[x_offset] = 1; // x = 1; stack_pointer[y_offset] = stack_pointer[x_offset]; // y = x; return_location = stack_pointer[y_offset]; // return y; stack_pointer += 2 * sizeof(int);
Insbesondere hat es eine Position, die auf den Anfang des Blocks lokaler Variablen zeigt, und jeder Zugriff auf die lokalen Variablen erfolgt als Offsets von dieser Position.
Bei verschachtelten Funktionen ist dies nicht mehr der Fall. Stattdessen hat eine Funktion nicht nur Zugriff auf ihre eigenen lokalen Variablen, sondern auch auf die Variablen, die für alle Funktionen lokal sind, in denen sie verschachtelt ist. Anstatt nur einen "stack_pointer" zu haben, aus dem ein Offset berechnet wird, muss er den Stack zurückgehen, um die stack_pointers lokal für die Funktionen zu finden, in denen er verschachtelt ist.
In einem trivialen Fall ist das auch nicht allzu schrecklich - wenn
bar
es in verschachtelt istfoo
,bar
kann es einfach den Stapel am vorherigen Stapelzeiger nachschlagen, um auf diefoo
Variablen zuzugreifen . Richtig?Falsch! Nun, es gibt Fälle, in denen dies wahr sein kann, aber es ist nicht unbedingt der Fall. Insbesondere
bar
könnte rekursiv sein, in welchem Fall ein gegebener Aufruf vonbar
Möglicherweise müssen Sie eine nahezu beliebige Anzahl von Ebenen im Stapel suchen, um die Variablen der umgebenden Funktion zu finden. Im Allgemeinen müssen Sie eines von zwei Dingen tun: Entweder Sie legen einige zusätzliche Daten auf den Stapel, damit dieser zur Laufzeit den Stapel erneut durchsuchen kann, um den Stapelrahmen der umgebenden Funktion zu finden, oder Sie übergeben effektiv einen Zeiger auf Der Stapelrahmen der umgebenden Funktion als versteckter Parameter für die verschachtelte Funktion. Oh, aber es gibt auch nicht unbedingt nur eine umgebende Funktion - wenn Sie Funktionen verschachteln können, können Sie sie wahrscheinlich (mehr oder weniger) beliebig tief verschachteln, sodass Sie bereit sein müssen, eine beliebige Anzahl versteckter Parameter zu übergeben. Das bedeutet, dass Sie normalerweise so etwas wie eine verknüpfte Liste von Stapelrahmen mit umgebenden Funktionen erhalten.Dies bedeutet jedoch, dass der Zugriff auf eine "lokale" Variable möglicherweise keine triviale Angelegenheit ist. Das Finden des richtigen Stapelrahmens für den Zugriff auf die Variable kann nicht trivial sein, sodass der Zugriff auf Variablen der umgebenden Funktionen (zumindest normalerweise) langsamer ist als der Zugriff auf wirklich lokale Variablen. Und natürlich muss der Compiler Code generieren, um die richtigen Stapelrahmen zu finden, über eine beliebige Anzahl von Stapelrahmen auf Variablen zuzugreifen und so weiter.
Dies ist die Komplexität, die C durch das Verbot verschachtelter Funktionen vermieden hat. Nun ist es sicher wahr, dass ein aktueller C ++ - Compiler eine ganz andere Art von Biest ist als ein C-Compiler aus den 1970er Jahren. Bei Dingen wie mehrfacher virtueller Vererbung muss sich ein C ++ - Compiler in jedem Fall mit Dingen befassen, die dieselbe allgemeine Natur haben (dh in solchen Fällen kann es auch nicht trivial sein, den Speicherort einer Basisklassenvariablen zu finden). Auf prozentualer Basis würde die Unterstützung verschachtelter Funktionen einem aktuellen C ++ - Compiler nicht viel Komplexität verleihen (und einige, wie z. B. gcc, unterstützen sie bereits).
Gleichzeitig wird auch selten viel Nutzen hinzugefügt. Insbesondere wenn Sie etwas definieren möchten, das sich wie eine Funktion innerhalb einer Funktion verhält, können Sie einen Lambda-Ausdruck verwenden. Was dies tatsächlich erzeugt, ist ein Objekt (dh eine Instanz einer Klasse), das den Funktionsaufrufoperator (
operator()
) überlastet, aber dennoch funktionsähnliche Funktionen bietet. Es macht das Erfassen (oder Nicht-Erfassen) von Daten aus dem umgebenden Kontext jedoch expliziter, wodurch vorhandene Mechanismen verwendet werden können, anstatt einen völlig neuen Mechanismus und ein Regelwerk für seine Verwendung zu erfinden.Fazit: Auch wenn es zunächst so aussieht, als wären verschachtelte Deklarationen schwierig und verschachtelte Funktionen trivial, ist mehr oder weniger das Gegenteil der Fall: Verschachtelte Funktionen sind tatsächlich viel komplexer zu unterstützen als verschachtelte Deklarationen.
quelle
Die erste ist eine Funktionsdefinition und nicht zulässig. Offensichtlich ist wt die Verwendung, eine Definition einer Funktion in eine andere Funktion zu setzen.
Aber die anderen beiden sind nur Erklärungen. Stellen Sie sich vor, Sie müssen die
int two(int bar);
Funktion innerhalb der Hauptmethode verwenden. Es wird jedoch unterhalb dermain()
Funktion definiert , sodass Sie diese Funktion bei der Funktionsdeklaration innerhalb der Funktion mit Deklarationen verwenden können.Gleiches gilt für den dritten. Mit Klassendeklarationen innerhalb der Funktion können Sie eine Klasse innerhalb der Funktion verwenden, ohne einen entsprechenden Header oder eine Referenz anzugeben.
int main() { // This is legal, but why would I want this? int two(int bar); //Call two int x = two(7); class three { int m_iBar; public: three(int bar):m_iBar(13 + bar) {} operator int() {return m_iBar;} }; //Use class three *threeObj = new three(); return 0; }
quelle
Diese Sprachfunktion wurde von C geerbt, wo sie in den frühen Tagen von C einen Zweck erfüllt hat (Scoping der Funktionsdeklaration vielleicht?) . Ich weiß nicht, ob diese Funktion von modernen C-Programmierern häufig verwendet wird, und ich bezweifle es aufrichtig.
Um die Antwort zusammenzufassen:
Es gibt keinen Zweck für diese Funktion in modernem C ++ (von dem ich zumindest weiß), es ist hier wegen der Abwärtskompatibilität von C ++ - zu C (ich nehme an :)).
Danke an den Kommentar unten:
Der Funktionsprototyp ist auf die Funktion beschränkt, in der er deklariert ist, sodass ein übersichtlicherer globaler Namespace erstellt werden kann - indem auf externe Funktionen / Symbole ohne Bezug genommen wird
#include
.quelle
Tatsächlich gibt es einen Anwendungsfall, der möglicherweise nützlich ist. Wenn Sie sicherstellen möchten, dass eine bestimmte Funktion aufgerufen wird (und Ihr Code kompiliert wird), können Sie unabhängig davon, was der umgebende Code deklariert, Ihren eigenen Block öffnen und den Funktionsprototyp darin deklarieren. (Die Inspiration stammt ursprünglich von Johannes Schaub, https://stackoverflow.com/a/929902/3150802 , über TeKa, https://stackoverflow.com/a/8821992/3150802 ).
Dies kann besonders nützlich sein, wenn Sie Header einfügen müssen, die Sie nicht steuern, oder wenn Sie ein mehrzeiliges Makro haben, das in unbekanntem Code verwendet werden kann.
Der Schlüssel ist, dass eine lokale Deklaration frühere Deklarationen im innersten umschließenden Block ersetzt. Während dies subtile Fehler verursachen kann (und meiner Meinung nach in C # verboten ist), kann es bewusst verwendet werden. Erwägen:
// somebody's header void f(); // your code { int i; int f(); // your different f()! i = f(); // ... }
Das Verknüpfen mag interessant sein, da die Header wahrscheinlich zu einer Bibliothek gehören, aber ich denke, Sie können die Linker-Argumente so anpassen, dass sie
f()
zu dem Zeitpunkt, zu dem diese Bibliothek berücksichtigt wird, in Ihre Funktion aufgelöst werden. Oder Sie weisen es an, doppelte Symbole zu ignorieren. Oder Sie verlinken nicht mit der Bibliothek.quelle
f
in deinem Beispiel definiert werden? Würde ich nicht einen Fehler bei der Neudefinition von Funktionen haben, da sich diese nur nach Rückgabetyp unterscheiden?void f()
undint f()
in C ++, da der Rückgabewert einer Funktion nicht Teil der Signatur der Funktion in C ++ ist. Ändern Sie die zweite Erklärung inint f(int)
und ich werde meine Ablehnung entfernen.i = f();
nach der Deklaration zu kompilierenvoid f()
. "Keine Unterscheidung" ist nur die halbe Wahrheit ;-). Ich habe tatsächlich nicht überladbare Funktionssignaturen verwendet, da sonst der gesamte Umstand in C ++ unnötig wäre, da zwei Funktionen mit unterschiedlichen Parametertypen / -nummern problemlos nebeneinander existieren könnten.Dies ist keine Antwort auf die OP-Frage, sondern eine Antwort auf mehrere Kommentare.
Ich bin mit diesen Punkten in den Kommentaren und Antworten nicht einverstanden: 1 dass verschachtelte Deklarationen angeblich harmlos sind und 2 dass verschachtelte Definitionen nutzlos sind.
1 Das wichtigste Gegenbeispiel für die angebliche Unbedenklichkeit verschachtelter Funktionsdeklarationen ist die berüchtigte Most Vexing Parse . IMO reicht die Ausbreitung der dadurch verursachten Verwirrung aus, um eine zusätzliche Regel zu rechtfertigen, die verschachtelte Deklarationen verbietet.
2 Das erste Gegenbeispiel für die angebliche Nutzlosigkeit verschachtelter Funktionsdefinitionen ist die häufige Notwendigkeit, dieselbe Operation an mehreren Stellen innerhalb genau einer Funktion auszuführen. Hierfür gibt es eine offensichtliche Problemumgehung:
private: inline void bar(int abc) { // Do the repeating operation } public: void foo() { int a, b, c; bar(a); bar(b); bar(c); }
Diese Lösung kontaminiert jedoch häufig genug die Klassendefinition mit zahlreichen privaten Funktionen, von denen jede in genau einem Aufrufer verwendet wird. Eine verschachtelte Funktionsdeklaration wäre viel sauberer.
quelle
Speziell auf diese Frage antworten:
Weil betrachten Sie diesen Code:
int main() { int foo() { // Do something return 0; } return 0; }
Fragen an Sprachdesigner:
foo()
für andere Funktionen verfügbar sein?int main(void)::foo()
?quelle
Ich wollte nur darauf hinweisen, dass Sie mit dem GCC-Compiler Funktionen innerhalb von Funktionen deklarieren können. Lesen Sie hier mehr darüber . Auch mit der Einführung von Lambdas in C ++ ist diese Frage jetzt etwas veraltet.
Die Möglichkeit, Funktionsheader in anderen Funktionen zu deklarieren, fand ich im folgenden Fall nützlich:
void do_something(int&); int main() { int my_number = 10 * 10 * 10; do_something(my_number); return 0; } void do_something(int& num) { void do_something_helper(int&); // declare helper here do_something_helper(num); // Do something else } void do_something_helper(int& num) { num += std::abs(num - 1337); }
Was haben wir hier? Grundsätzlich haben Sie eine Funktion, die von main aufgerufen werden soll. Sie deklarieren sie also wie gewohnt weiter. Aber dann merkt man, dass diese Funktion auch eine andere Funktion benötigt, um sie bei ihrer Arbeit zu unterstützen. Anstatt diese Hilfsfunktion über main zu deklarieren, deklarieren Sie sie innerhalb der Funktion, die sie benötigt, und dann kann sie nur von dieser Funktion und dieser Funktion aufgerufen werden.
Mein Punkt ist, dass das Deklarieren von Funktionsheadern innerhalb von Funktionen eine indirekte Methode der Funktionskapselung sein kann, die es einer Funktion ermöglicht, einige Teile ihrer Arbeit zu verbergen, indem sie an eine andere Funktion delegiert, die nur ihr bekannt ist, und fast die Illusion einer verschachtelten Funktion vermittelt Funktion .
quelle
Verschachtelte Funktionsdeklarationen sind wahrscheinlich für 1. Weiterleiten von Referenzen zulässig. 2. Um einen Zeiger auf Funktion (en) deklarieren und andere Funktion (en) in einem begrenzten Bereich weitergeben zu können.
Verschachtelte Funktionsdefinitionen sind wahrscheinlich aufgrund von Problemen wie 1. Optimierung 2. Rekursion (umschließende und verschachtelte definierte Funktion (en)) 3. Wiedereintritt 4. Parallelität und andere Multithread-Zugriffsprobleme nicht zulässig.
Nach meinem begrenzten Verständnis :)
quelle