2016-05-08 6 views
11

Sorry, es ist eine langatmige Frage, aber lassen Sie mich es brechen:Wie hoch ist die Lebensdauer des Zieles von Pointer-to-Function auf ein Lambda?

Ist der C++ Standard gewährleistet, dass:

void (*Ptr)(void) = [] {}; 
return Ptr; 

noch Verhalten definiert werden?

Ich verstehe, dass für eine Schließung, wird es definiert werden, weil das Schließobjekt Wert verschoben/kopiert wird; Aber obwohl ich weiß, dass eine "normale" Funktion unendlich/keine Lebensdauer hat, hat das Ziel von Ptr dasselbe? Oder wird es bei jeder Instantiierung des Lambda zerstört und neu erschaffen?

Der Grund, der mich interessiert, ist, ich kann Lambdas nicht als Rückrufe verwenden, wenn nicht. Ich möchte es wissen.

+0

Interessante Frage. Da der Zeiger auf ein * static * "invoker" -Mitglied zeigt, hat das statische Mitglied keine andere Wahl, als ein Dummy-Objekt zu verwenden, um 'operator()' des Lambda aufzurufen (weil das letztere nicht-statisch ist). Dieser Dummy sollte entweder lokal für den "Invoker" oder statisch sein, d. H. Er sollte in dem obigen Kontext gut funktionieren. Aber ich sehe diese Garantie nicht sofort in der Sprachspezifikation. Die Sprachspezifikation postuliert nicht einmal die Existenz dieses Dummy-Objekts. – AnT

+4

@ant Warum? Es muss den gleichen Code ausführen. Warum muss es in 'operator()' sein? 'operator()' könnte durch Aufruf der statischen Funktion implementiert werden, die 'operator void (*)()()' gibt – Yakk

+4

zurück Ich wünschte, alle langatmigen Fragen wären so kurz –

Antwort

2

eine Lambda-Funktion ist nur ein syntaktischer Zucker für eine echte Funktion oder Funktor (d. H. Ein Objekt mit operator() und einige Mitglieder, in der Regel in Bauzeit definiert). Eine solche Funktion oder Methode ist also während der Kompilierung statisch definiert.

Obwohl der Standard möglicherweise keine exakte Implementierung spezifiziert, wie @NicolBolas es ausdrückte, scheint es, dass praktische Implementierungen der strengen Richtlinie folgen: Ein Lambda ohne Kontext kann in einen einfachen Funktionszeiger umgewandelt werden, kein Zwischenobjekt ist geschaffen weder an der Stelle der Lambda-Definition, noch an der Stelle der Anrufung. Ich habe es (noch einmal) für gcc und clang überprüft und ich bin mir fast sicher, dass MSVC das gleiche tut.

Hinweis: Der Rest ist über Lambdas mit Kontexten, und obwohl für mich es interessanter und praktischer Fall scheint, befasst sich die Frage explizit mit Kontext-less lambdas.

Ein Kontext wird in einem Lambda gespeichert (denken Sie an ein Funktor-Objekt mit einigen sinnvollen Argumenten, die bei der Objektkonstruktion empfangen werden). Wenn Sie also einige Verweise oder Zeiger auf den Kontext übergeben, verlängern diese Verweise und Zeiger (z. B. this) nicht automatisch die Lebensdauern ihrer entsprechenden Entitäten. Aus diesem Grund sollten Sie besonders vorsichtig sein, wenn Sie ein Lambda in einem anderen als dem in der Definition definierten Bereich speichern.

Ein Beispiel für Probleme im Zusammenhang mit Lambda und seiner Definition des Kontextbereichs finden Sie in dieser resolved issue. Überprüfen Sie den Fix, um zu sehen, was getan wurde, um gespeicherte Lambdas mit sicheren Kontexten zu erstellen.

+1

"Ein Lambda mit einem leeren Kontext wird immer in eine anonyme statische Funktion umgewandelt" Was? Nein. 'Auto f = [] {}; static_assert (std :: ist_same {}, "nein"); ' – Yakk

+0

Ok, du hast Recht. Ein solches Lambda kann jedoch einem entsprechenden Funktionszeiger zugeordnet werden, wie im Beispiel des Themenstarter. – user3159253

+4

Ihre zweite Para scheint über Lambdas mit Capture zu sprechen, aber OP Frage ist über captureless Lambda –

5

Objekte haben Lebensdauern; Funktionen nicht. Funktionen leben nicht oder sterben nicht; sie existieren immer. Als solche kann eine Funktion nicht "außerhalb des Gültigkeitsbereichs" gehen, noch kann die Funktion, auf die durch einen zuvor gültigen Funktionszeiger gezeigt wird, verschwinden. Unabhängig davon, woher sie kommen, sind Funktionszeiger immer gültig.

Nun ignoriert dies dynamisches Laden und so weiter, aber das ist extra-Standard-Verhalten.

Der Funktionszeiger, den Sie von einem Lambda erhalten, ist ein Funktionszeiger. Es ist nicht besonders oder magisch. Es verhält sich daher nicht anders als jeder andere Funktionszeiger.


Ist es möglich, dass das Ergebnis der Umstellung auf void (*)() zeigt auf etwas, das eine Memberfunktion aufruft, auf ein Objekt gebunden?

Das ist eine viel komplexere Frage.Eine, über die der Standard eher unterspezifiziert scheint. Der Standard sagt nur:

die Adresse einer Funktion, die, wenn sie aufgerufen wird, den gleichen Effekt hat wie das Aufrufen des Funktionsaufrufoperators des Schließungstyps.

Was "der gleiche Effekt" genau bedeutet, ist die Frage. Man könnte argumentieren, dass "derselbe Effekt" bedeutet, zu tun, was der Funktionsaufrufoperator getan hätte, indem er die gleiche Folge von Anweisungen ausführt. Man könnte auch argumentieren, dass "derselbe Effekt" bedeuten würde, das Schließobjekt selbst aufzurufen.

Der letztere Fall klingt vielleicht schwierig zu implementieren, aber denken Sie daran, dass Compiler Magie verwendet werden kann. Die Schließung könnte einen instanzspezifischen Funktionszeiger zurückgeben, der auf Anfrage durch die Schließung zugewiesen wird. Oder etwas Ähnliches.

Der nicht normative Text scheint in dieser Angelegenheit nicht sehr aufschlussreich zu sein. Es ist ein Beispiel für einen Verschluss für ein (generisch) Lambda, das sagt dieser:

template<class T> auto operator()(T t) const { ... } 
template<class T> static auto lambda_call_operator_invoker(T a) { 
// forwards execution to operator()(a) and therefore has 
// the same return type deduced 
... 
} 

Der Kommentar in Frage schlägt vor Weiterleitung, aber das würde erfordern, dass der statische Aufruf eine neue Instanz des Konstrukt Lambda für die Weiterleitung mit.

Insgesamt macht der Standard es nicht klar, ob der Aufruf des generierten Funktionszeigers nach der Zerstörung der Schließung zulässig ist.

+0

Wie passen Elementfunktionen dazu? –

+2

@M.M: Pointer-to-Members sind eine andere Sache (und sind off-topic), aber sie folgen immer noch der gleichen Idee. "Mitglieder" sind keine Objekte. Als solche haben sie keine Lebenszeiten. Mitgliedszeiger sind immer gültig. Sie benötigen jedoch immer noch ein Objekt, um sie aufzurufen. Dieses Objekt kann gültig sein oder nicht. –

+1

Elementfunktionen sind im Wesentlichen gewöhnliche Funktionen mit einem zusätzlichen versteckten Argument (this). Sobald Sie also ein Programm geladen haben, sind alle Mitgliederfunktionen an ihren Plätzen, aber es sind noch keine Objekte vorhanden. – user3159253

Verwandte Themen