Das fragliche Lambda hat eigentlich keinen Staat .
Untersuchen:
struct lambda {
auto operator()() const { return 17; }
};
Und wenn ja lambda f;
, ist es eine leere Klasse. Das Obige lambda
ä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 Name lambda
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!
auto f = [](){ return 17; };
f = [](){ return -42; };
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 gespeichert f
, nicht im Wert von f
!
Wenn Sie dies getan haben:
int(*f)() = [](){ return 17; };
oder dieses:
std::function<int()> f = [](){ return 17; };
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 aufrufen f
. Und sizeof(f)
ist nicht mehr 1
, sondern eher sizeof(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 eine std::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ätte std::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öße 0
?
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 Elemente sizeof(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 haben sizeof
von 1
. 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.)
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
das sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
ist sizeof(int)
(nun, das oben Genannte ist illegal, weil Sie kein Lambda in einem nicht bewerteten Kontext erstellen können: Sie müssen ein benanntes auto toy = make_toy(blah);
dann erstellen sizeof(blah)
, aber das ist nur Rauschen). sizeof([]{std::cout << "hello world!\n"; })
ist noch 1
(ähnliche Qualifikationen).
Wenn wir einen anderen Spielzeugtyp erstellen:
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
Dies hat zwei Kopien des Lambda. Da sie nicht die gleiche Adresse teilen können, sizeof(toy2(some_lambda))
ist 2
!
struct
mit einemoperator()
)