Ich arbeite mit der Erinnerung an einige Lambdas in C ++, aber ich bin ein bisschen verwirrt über ihre Größe.
Hier ist mein Testcode:
#include <iostream>
#include <string>
int main()
{
auto f = [](){ return 17; };
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;
}
Sie können es hier ausführen: http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598
Die Ouptut ist:
17
0x7d90ba8f626f
1
Dies deutet darauf hin, dass die Größe meines Lambda 1 ist.
Wie ist das möglich?
Sollte das Lambda nicht zumindest ein Hinweis auf seine Umsetzung sein?
struct
mit einemoperator()
)Antworten:
Das fragliche Lambda hat eigentlich keinen Staat .
Untersuchen:
Und wenn ja
lambda f;
, ist es eine leere Klasse. Das Obigelambda
ähnelt nicht nur funktional Ihrem Lambda, es ist auch (im Grunde) so, wie Ihr Lambda implementiert wird! (Es wird auch eine implizite Umwandlung in den Funktionszeigeroperator benötigt, und der Namelambda
wird durch eine vom Compiler generierte Pseudo-Guid ersetzt.)In C ++ sind Objekte keine Zeiger. Sie sind tatsächliche Dinge. Sie belegen nur den Speicherplatz, der zum Speichern der Daten in ihnen erforderlich ist. Ein Zeiger auf ein Objekt kann größer als ein Objekt sein.
Während Sie sich dieses Lambda vielleicht als Zeiger auf eine Funktion vorstellen, ist dies nicht der Fall. Sie können das nicht
auto f = [](){ return 17; };
einer anderen Funktion oder einem anderen Lambda zuweisen!Das oben Genannte ist illegal . Es ist kein Platz
f
zum Speichern der Funktion, die aufgerufen werden soll - diese Informationen werden in der Art von gespeichertf
, nicht im Wert vonf
!Wenn Sie dies getan haben:
oder dieses:
Sie speichern das Lambda nicht mehr direkt. In beiden Fällen
f = [](){ return -42; }
ist dies legal. In diesen Fällen speichern wir also, welche Funktion wir im Wert von aufrufenf
. Undsizeof(f)
ist nicht mehr1
, sondern ehersizeof(int(*)())
oder größer (im Grunde genommen Zeigergröße oder größer, wie Sie erwarten.std::function
Hat eine vom Standard implizierte Mindestgröße (sie müssen in der Lage sein, Callables bis zu einer bestimmten Größe in sich selbst zu speichern), die ist in der Praxis mindestens so groß wie ein Funktionszeiger).In diesem
int(*f)()
Fall speichern Sie einen Funktionszeiger auf eine Funktion, die sich so verhält, als hätten Sie dieses Lambda aufgerufen. Dies funktioniert nur für zustandslose Lambdas (solche mit einer leeren[]
Erfassungsliste).In diesem
std::function<int()> f
Fall erstellen Sie einestd::function<int()>
Instanz einer Klassenlöschklasse , die (in diesem Fall) die Platzierung new verwendet, um eine Kopie des Lambda der Größe 1 in einem internen Puffer zu speichern (und, wenn ein größeres Lambda übergeben wurde (mit mehr Status) ), würde Heap-Zuordnung verwenden).Vermutlich ist so etwas wahrscheinlich das, was Sie denken. Dass ein Lambda ein Objekt ist, dessen Typ durch seine Signatur beschrieben wird. In C ++ wurde beschlossen, Lambdas kostengünstige Abstraktionen über die Implementierung des manuellen Funktionsobjekts zu erstellen. Auf diese Weise können Sie ein Lambda an einen
std
Algorithmus (oder einen ähnlichen) übergeben und dessen Inhalt für den Compiler vollständig sichtbar machen, wenn er die Algorithmusvorlage instanziiert. Wenn ein Lambda einen Typ wie hättestd::function<void(int)>
, wäre sein Inhalt nicht vollständig sichtbar und ein handgefertigtes Funktionsobjekt könnte schneller sein.Das Ziel der C ++ - Standardisierung ist die Programmierung auf hoher Ebene ohne Overhead gegenüber handgefertigtem C-Code.
Jetzt, da Sie verstehen, dass Sie
f
tatsächlich staatenlos sind, sollte es eine andere Frage in Ihrem Kopf geben: Das Lambda hat keinen Zustand. Warum hat es keine Größe0
?Da ist die kurze Antwort.
Alle Objekte in C ++ müssen unter dem Standard eine Mindestgröße von 1 haben, und zwei Objekte desselben Typs können nicht dieselbe Adresse haben. Diese sind miteinander verbunden, da bei einem Array vom Typ
T
die Elementesizeof(T)
voneinander getrennt sind.Jetzt, da es keinen Zustand hat, kann es manchmal keinen Platz einnehmen. Dies kann nicht passieren, wenn es "alleine" ist, aber in einigen Kontexten kann es passieren.
std::tuple
und ähnlicher Bibliothekscode nutzt diese Tatsache aus. So funktioniert es:Da ein Lambda einer Klasse mit
operator()
überladenen entspricht, sind zustandslose Lambdas (mit einer[]
Erfassungsliste) alle leere Klassen. Sie habensizeof
von1
. Wenn Sie von ihnen erben (was zulässig ist!), Nehmen sie keinen Platz ein , solange dies keine Adresskollision vom gleichen Typ verursacht . (Dies wird als Leerbasisoptimierung bezeichnet.)das
sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
istsizeof(int)
(nun, das oben Genannte ist illegal, weil Sie kein Lambda in einem nicht bewerteten Kontext erstellen können: Sie müssen ein benanntesauto toy = make_toy(blah);
dann erstellensizeof(blah)
, aber das ist nur Rauschen).sizeof([]{std::cout << "hello world!\n"; })
ist noch1
(ähnliche Qualifikationen).Wenn wir einen anderen Spielzeugtyp erstellen:
Dies hat zwei Kopien des Lambda. Da sie nicht die gleiche Adresse teilen können,
sizeof(toy2(some_lambda))
ist2
!quelle
()
hinzugefügt.Ein Lambda ist kein Funktionszeiger.
Ein Lambda ist eine Instanz einer Klasse. Ihr Code entspricht ungefähr:
Die interne Klasse, die ein Lambda darstellt, hat keine Klassenmitglieder, daher
sizeof()
ist sie 1 (sie kann aus an anderer Stelle hinreichend angegebenen Gründen nicht 0 sein ).Wenn Ihr Lambda einige Variablen erfasst, entsprechen diese den Klassenmitgliedern, und Sie
sizeof()
geben dies entsprechend an.quelle
sizeof()
nicht 0 sein kann?Ihr Compiler übersetzt das Lambda mehr oder weniger in den folgenden Strukturtyp:
Da diese Struktur keine nicht statischen Elemente hat, hat sie dieselbe Größe wie eine leere Struktur
1
.Das ändert sich, sobald Sie Ihrem Lambda eine nicht leere Erfassungsliste hinzufügen:
Welches wird zu übersetzen
Da die generierte Struktur jetzt ein nicht statisches
int
Element für die Erfassung speichern muss, wächst ihre Größe aufsizeof(int)
. Die Größe wächst weiter, wenn Sie mehr Material aufnehmen.(Bitte nehmen Sie die Strukturanalogie mit einem Körnchen Salz. Es ist zwar eine gute Möglichkeit, darüber nachzudenken, wie Lambdas intern funktionieren, dies ist jedoch keine wörtliche Übersetzung dessen, was der Compiler tun wird.)
quelle
Nicht unbedingt. Gemäß dem Standard ist die Größe der eindeutigen, unbenannten Klasse implementierungsdefiniert . Auszug aus [expr.prim.lambda] , C ++ 14 (Schwerpunkt Mine):
In Ihrem Fall - für den von Ihnen verwendeten Compiler - erhalten Sie eine Größe von 1, was nicht bedeutet, dass er behoben ist. Sie kann zwischen verschiedenen Compiler-Implementierungen variieren.
quelle
Von http://en.cppreference.com/w/cpp/language/lambda :
Von http://en.cppreference.com/w/cpp/language/sizeof
quelle