Warum kann ich eine Funktion nicht in einer anderen Funktion definieren?

80

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, twowas nutzlos und threeweitaus 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.

Jonathan Mee
quelle
3
Die erste, oneeine Funktion ist , Definition , sind die anderen zwei Erklärungen .
Einige Programmierer Typ
9
Ich denke, Sie haben die Begriffe falsch verstanden - Sie möchten fragen: "Was bringt es uns , eine Funktion im Code zu deklarieren, aber nicht zu definieren ?". Und während wir gerade dabei sind, meinen Sie wahrscheinlich "innerhalb einer Funktion ". Es ist alles "Code".
Peter - Stellen Sie Monica am
14
Wenn Sie sich fragen, warum die Sprache Macken und Inkonsistenzen aufweist: Weil sie sich über mehrere Jahrzehnte durch die Arbeit vieler Menschen mit vielen verschiedenen Ideen aus Sprachen entwickelt hat, die zu unterschiedlichen Zeiten aus unterschiedlichen Gründen erfunden wurden. Wenn Sie sich fragen, warum es diese besondere Eigenart hat: Weil (bisher) niemand dachte, dass lokale Funktionsdefinitionen nützlich genug sind, um sie zu standardisieren.
Mike Seymour
4
@ MikeSeymour hat es richtig gemacht. C ist nicht so gut strukturiert wie beispielsweise Pascal und erlaubt immer nur Funktionsdefinitionen der obersten Ebene. Der Grund ist also historisch und es fehlt die Notwendigkeit, ihn zu ändern. Diese Funktion Erklärungen möglich sind , ist nur eine Folge von scoped Erklärungen im Allgemeinen möglich ist. Das zu verbieten für Funktionen hätte eine zusätzliche Regel bedeutet.
Peter - Stellen Sie Monica am
3
@JonathanMee: Wahrscheinlich, weil Deklarationen im Allgemeinen in Blöcken zulässig sind und es keinen besonderen Grund gibt, Funktionsdeklarationen ausdrücklich zu verbieten. Es ist einfacher, nur eine Erklärung ohne Sonderfälle zuzulassen. Aber "warum" ist keine wirklich beantwortbare Frage; Die Sprache ist, was sie ist, denn so hat sie sich entwickelt.
Mike Seymour

Antworten:

41

Es ist nicht offensichtlich, warum dies onenicht erlaubt ist. verschachtelte Funktionen wurden vor langer Zeit in N0295 vorgeschlagen, die besagt:

Wir diskutieren die Einführung verschachtelter Funktionen in C ++. Verschachtelte Funktionen sind gut verstanden und ihre Einführung erfordert weder von Compiler-Anbietern noch von Programmierern oder dem Komitee einen geringen Aufwand. Verschachtelte Funktionen bieten erhebliche Vorteile, [...]

Natürlich wurde dieser Vorschlag abgelehnt, aber da wir online keine Sitzungsprotokolle zur Verfügung 1993haben, 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:

Ein Artikel [Bre88] und der Vorschlag N0295 an das C ++ - Komitee [SH93] schlagen vor, verschachtelte Funktionen zu C ++ hinzuzufügen. Verschachtelte Funktionen ähneln Lambda-Ausdrücken, werden jedoch als Anweisungen innerhalb eines Funktionskörpers definiert, und der resultierende Abschluss kann nur verwendet werden, wenn diese Funktion aktiv ist. Diese Vorschläge beinhalten auch nicht das Hinzufügen eines neuen Typs für jeden Lambda-Ausdruck, sondern deren Implementierung eher wie normale Funktionen, einschließlich der Erlaubnis, dass eine spezielle Art von Funktionszeiger auf sie verweist. Beide Vorschläge stammen aus der Zeit vor dem Hinzufügen von Vorlagen zu C ++ und erwähnen daher nicht die Verwendung verschachtelter Funktionen in Kombination mit generischen Algorithmen. Diese Vorschläge haben auch keine Möglichkeit, lokale Variablen in einen Abschluss zu kopieren, sodass die von ihnen erzeugten verschachtelten Funktionen außerhalb ihrer umschließenden Funktion vollständig unbrauchbar sind

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:

// This is legal, but why would I want this?
int two(int bar);

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)
}
Shafik Yaghmour
quelle
1
Frage zu dem von Ihnen angegebenen 3.4.1-Beispiel: Könnte der Aufrufer in main nicht einfach schreiben ::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 sollte g?
Peter - Stellen Sie Monica am
@PeterSchneider Ich habe dort eine zu starke Aussage gemacht, ich habe sie angepasst.
Shafik Yaghmour
1
Ich möchte den Kommentar hier hinzufügen: Diese Antwort wurde nicht akzeptiert, weil sie am besten erklärt hat, warum Deklarationen in Codefunktionen zulässig sind. aber weil es die beste Arbeit geleistet hat, um zu beschreiben, warum Definitionen in Codefunktionen nicht erlaubt sind, war das die eigentliche Frage. Insbesondere wird erläutert, warum sich die hypothetische Implementierung von In-Code-Funktionen von der Implementierung von Lambdas unterscheiden würde. +1
Jonathan Mee
1
@ JonathanMee: Wie in aller Welt: "... wir haben keine mögliche Quelle für die Begründung dieser Ablehnung." qualifizieren Sie sich als die beste Aufgabe, um zu beschreiben, warum verschachtelte Funktionsdefinitionen nicht erlaubt sind (oder überhaupt versuchen, sie überhaupt zu beschreiben?)
Jerry Coffin
@JerryCoffin Die Antwort enthielt die offizielle Begründung, warum Lambdas bereits eine Supermenge von Codefunktionsdefinitionen sind, die ihre Implementierung unnötig machen: "Der resultierende Abschluss kann nur verwendet werden, wenn diese Funktion aktiv ist ... Auch diese Vorschläge können nicht kopiert werden lokale Variablen in einen Abschluss. " Ich gehe davon aus, dass Sie sich fragen, warum Ihre Analyse der zusätzlichen Komplexität von Compilern nicht die Antwort war, die ich akzeptiert habe. Wenn ja: Sie sprechen von der Schwierigkeit, die Lambdas bereits erreichen, in Code-Definitionen könnten sie eindeutig genau wie Lambdas implementiert werden.
Jonathan Mee
31

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.

MM
quelle
10
"Dies wird allgemein als schlechte Idee angesehen" - Zitieren erforderlich.
Richard Hodges
4
@RichardHodges: Nun, Funktionsdeklarationen gehören in Header-Dateien und die Implementierung in .c- oder .cpp-Dateien. Wenn diese Deklarationen in Funktionsdefinitionen enthalten sind, verstößt dies gegen eine dieser beiden Richtlinien.
MSalters
2
Wie verhindert es, dass die Deklaration von der Definition abweicht?
Richard Hodges
1
@JonathanMee: Ich sage, wenn die von Ihnen verwendete Deklaration dort, wo die Funktion definiert ist, nicht verfügbar ist, überprüft der Compiler möglicherweise nicht, ob die Deklaration mit der Definition übereinstimmt. Möglicherweise haben Sie eine lokale Deklaration some_type f();und eine Definition in einer anderen Übersetzungseinheit another_type f() {...}. Der Compiler kann Ihnen nicht sagen, dass diese nicht übereinstimmen, und ein Aufruf fmit 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.
Mike Seymour
6
Ich denke, was Sie sagen, ist, dass die übliche Praxis, Funktionsdeklarationen in Header-Dateien einzufügen, im Allgemeinen nützlich ist. Ich glaube nicht, dass jemand damit nicht einverstanden wäre. Was ich keinen Grund sehe, ist die Behauptung, dass die Deklaration einer externen Funktion im Funktionsumfang "allgemein als schlechte Idee angesehen wird".
Richard Hodges
23

In dem von Ihnen angegebenen Beispiel void two(int)wird es als externe Funktion deklariert, wobei diese Deklaration nur im Rahmen der mainFunktion gültig ist .

Dies ist sinnvoll, wenn Sie den Namen twonur innerhalb verfügbar machen möchten, um main()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

c++ main.cpp foo.cpp 

Es wird kompiliert und ausgeführt, und das Programm gibt wie erwartet 0 zurück.

Richard Hodges
quelle
Müsste nicht twoauch in der Datei definiert werden, wodurch die Verschmutzung trotzdem verursacht wird?
Jonathan Mee
1
@ JonathanMee nein, two()könnte in einer völlig anderen Kompilierungseinheit definiert werden.
Richard Hodges
Ich brauche Hilfe, um zu verstehen, wie das funktionieren würde. Müssten Sie nicht den Header einfügen, in dem er deklariert wurde? An welchem ​​Punkt würde es deklariert werden, richtig? Ich sehe nur nicht, wie Sie es im Code definieren können, und irgendwie nicht die Datei einschließen, die es deklariert?
Jonathan Mee
5
@ JonathanMee Header haben nichts Besonderes. Sie sind nur ein geeigneter Ort, um Erklärungen abzugeben. Eine Deklaration innerhalb einer Funktion ist genauso gültig wie eine Deklaration innerhalb eines Headers. Nein, Sie müssten also nicht den Header dessen einfügen, mit dem Sie verknüpfen (möglicherweise gibt es überhaupt keinen Header).
Cubic
1
@ JonathanMee In der C / C ++ - Sprache sind Definition und Implementierung dasselbe. Sie können eine Funktion so oft deklarieren, wie Sie möchten, aber Sie können sie nur einmal definieren. Die Deklaration muss sich nicht in einer Datei befinden, die auf .h endet. Sie können eine Datei use.cpp mit einer Funktionsleiste haben, die foo aufruft (deklariert foo in ihrem Hauptteil), und eine Datei liefert.cpp, die foo definiert. und es würde gut funktionieren, solange Sie den Verknüpfungsschritt nicht durcheinander bringen.
Cubic
18

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 bares in verschachtelt ist foo, barkann es einfach den Stapel am vorherigen Stapelzeiger nachschlagen, um auf die fooVariablen zuzugreifen . Richtig?

Falsch! Nun, es gibt Fälle, in denen dies wahr sein kann, aber es ist nicht unbedingt der Fall. Insbesondere barkönnte rekursiv sein, in welchem ​​Fall ein gegebener Aufruf vonbarMö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.

Jerry Sarg
quelle
5

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 der main()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;
}
ANjaNA
quelle
2
Was ist "Verzögerung"? Meinst du "Erklärung"?
Peter Mortensen
4

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.

mr.pd
quelle
Der Zweck besteht darin, den Umfang des Namens zu kontrollieren, um eine globale Verschmutzung des Namespace zu vermeiden.
Richard Hodges
Ok, ich nehme an, es ist nützlich für Situationen, in denen Sie auf externe Funktionen / Symbole verweisen möchten, ohne den globalen Namespace mit #include zu verschmutzen! Vielen Dank für den Hinweis. Ich werde eine Bearbeitung vornehmen.
mr.pd
4

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.

Peter - Monica wieder einsetzen
quelle
Also hilf mir hier raus, wo würde fin deinem Beispiel definiert werden? Würde ich nicht einen Fehler bei der Neudefinition von Funktionen haben, da sich diese nur nach Rückgabetyp unterscheiden?
Jonathan Mee
@ JonathanMee hmmm ... f () könnte in einer anderen Übersetzungseinheit definiert werden, dachte ich. Aber wahrscheinlich würde der Linker zurückschrecken, wenn Sie auch gegen die angenommene Bibliothek verlinken würden, ich nehme an, Sie haben Recht. Das kann man also nicht machen ;-) oder muss zumindest mehrere Definitionen ignorieren.
Peter - Stellen Sie Monica am
Schlechtes Beispiel. Es gibt keinen Unterschied zwischen void f()und int f()in C ++, da der Rückgabewert einer Funktion nicht Teil der Signatur der Funktion in C ++ ist. Ändern Sie die zweite Erklärung in int f(int)und ich werde meine Ablehnung entfernen.
David Hammen
@DavidHammen Versuchen Sie i = f();nach der Deklaration zu kompilieren void 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.
Peter - Reinstate Monica
@DavidHammen In der Tat glaube ich, dass wir nach dem Lesen von Shafiks Antwort drei Fälle haben: 1. Die Signatur unterscheidet sich in den Parametern. Kein Problem in C ++, einfache Überladung und Regeln für die beste Übereinstimmung funktionieren. 2. Die Unterschrift unterscheidet sich überhaupt nicht. Kein Problem auf Sprachebene; Die Funktion wird durch Verknüpfen mit der gewünschten Implementierung aufgelöst. 3. Der Unterschied besteht nur im Rückgabetyp. Wie gezeigt, gibt es auf Sprachebene ein Problem. Überlastungsauflösung funktioniert nicht; Wir müssen eine Funktion mit einer anderen Signatur deklarieren und entsprechend verknüpfen.
Peter - Reinstate Monica
3

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.

Michael
quelle
1
Ich denke, dies ist eine gute Zusammenfassung der Motivation meiner Frage. Wenn Sie sich die Originalversion ansehen, habe ich MVP zitiert, aber ich werde in den Kommentaren (meiner eigenen Frage) immer wieder überstimmt, dass MVP irrelevant ist :( Ich kann einfach nicht herausfinden, wie die potenziell schädlichen Code-Deklarationen immer noch hier sind , aber die potenziell nützlichen in Code-Definitionen sind nicht. Ich habe Ihnen eine +1 für die nützlichen Beispiele gegeben.
Jonathan Mee
2

Speziell auf diese Frage antworten:

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.

Weil betrachten Sie diesen Code:

int main()
{
  int foo() {

    // Do something
    return 0;
  }
  return 0;
}

Fragen an Sprachdesigner:

  1. Sollte foo()für andere Funktionen verfügbar sein?
  2. Wenn ja, wie sollte der Name sein? int main(void)::foo()?
  3. (Beachten Sie, dass 2 in C, dem Urheber von C ++, nicht möglich wäre.)
  4. Wenn wir eine lokale Funktion wollen, haben wir bereits eine Möglichkeit - machen Sie sie zu einem statischen Mitglied einer lokal definierten Klasse. Sollten wir also eine andere syntaktische Methode hinzufügen, um das gleiche Ergebnis zu erzielen? Warum das tun? Würde dies nicht den Wartungsaufwand für C ++ - Compilerentwickler erhöhen?
  5. Und so weiter...
Richard Hodges
quelle
Offensichtlich ist dieses Verhalten für Lambdas definiert? Warum nicht im Code definierte Funktionen?
Jonathan Mee
Ein Lambda ist lediglich eine Abkürzung zum Schreiben eines Funktionsobjekts. Der Sonderfall eines Lambda, der keine Argumente erfasst, entspricht einer lokalen Funktionsdefinition, ebenso wie das Schreiben eines Funktionsobjekts ohne Datenelemente.
Richard Hodges
Ich habe nur darauf hingewiesen, dass Lambdas und in Code deklarierte Funktionen bereits alle Ihre Punkte verwerfen. Es sollte keine Erhöhung der "Belastung" sein.
Jonathan Mee
@JonathanMee Wenn Sie sich stark dafür fühlen, senden Sie auf jeden Fall einen RFC an das c ++ - Standardkomitee.
Richard Hodges
Shafik Yaghmours Antwort deckte das bereits Erreichte ab. Ich persönlich würde es begrüßen, wenn die Möglichkeit, Funktionen im Code zu deklarieren, entfernt würde, wenn wir sie nicht definieren könnten. Die Antwort von Richard Hodges erklärt sehr gut, warum wir immer noch die Fähigkeit benötigen, in der Code-Deklaration zu deklarieren.
Jonathan Mee
1

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 .

smac89
quelle
Ich habe verstanden, dass wir ein Lambda inline definieren können. Ich habe verstanden, dass wir eine Funktion inline deklarieren können, aber das ist der Ursprung der ärgerlichsten Analyse . Meine Frage war also, ob der Standard Funktionen beibehalten wird, die nur dazu dienen, Wut in Programmierern hervorzurufen, sollten Programmierer nicht in der Lage sein, diese zu definieren Funktion auch inline? Die Antwort von Richard Hodges half mir, den Ursprung dieses Problems zu verstehen.
Jonathan Mee
0

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 :)

NeutronStar
quelle