2012-12-09 11 views
7

Aus meiner Erfahrung scheint es, dass entweder:C++ Adresse von Lambda-Objekten als Parameter an Funktionen

  • Ein Lambda-Ausdruck in einem Funktionsaufruf erstellt unmittelbar nach dem Aufruf zerstört
  • Aufruf eine Funktion, die ein std::function erwartet erzeugt ein temporäres Objekt (std :: function) aus dem Lambda, und dieses Objekt wird nach dem Aufruf zerstört

Dieses Verhalten kann mit dem folgenden Code-Schnipsel zu beachten:

const function<void()>* pointer; 

void a(const function<void()> & f) 
{ 
    pointer = &f; 
} 

void b() 
{ 
    (*pointer)(); 
} 

int main() 
{ 
    int value = 1; 
    std::cout << &value << std::endl; 

    // 1: this works  
    function<void()> f = [&]() { std::cout << &value << std::endl; }; 
    a(f); 

    // 2: this doesn't 
    a([&]() { std::cout << &value << std::endl; }); 

    /* modify the stack*/ 
    char data[1024]; 
    for (int i = 0; i < 1024; i++) 
     data[i] = i % 4; 

    b(); 

    return 0; 
} 

Was genau passiert eigentlich im zweiten Fall? Gibt es eine korrekte Möglichkeit, a() aufzurufen, ohne ein explizites Objekt std::function zu erstellen?

Edit:: Diese beiden Versionen (1 und 2) kompiliert nur Recht, sondern führt in verschiedenen Ausgängen:

Version 1:

0x7fffa70148c8 
0x7fffa70148c8 

Version 2:

0x7fffa70148c8 
0 
+1

Was meinst du damit, dass der zweite Fall "nicht funktioniert"? Kompiliert es? Stürzt es ab? Schreibt es "funktioniert nicht" zu Ihrem Drucker? – jalf

+1

@jalf: Du hast vergessen: Stoppt es und fängt Feuer. – Grizzly

+1

/* Ändern des Stapels */Die meisten Compiler weisen den erforderlichen Platz für ** alle ** lokalen Variablen bei der Funktionseingabe vor. –

Antwort

2

Wenn Sie ein temporäres erstellen, ist es am Ende der Zeile gegangen. Dies bedeutet, dass das Speichern eines Zeigers eine schlechte Idee ist, wie Sie richtig festgestellt haben.

Wenn Sie einen Zeiger auf eine std::function (oder etwas anderes wirklich) speichern möchten, müssen Sie sicherstellen, dass die Lebensdauer nicht endet, bevor Sie den Zeiger nicht mehr verwenden. Dies bedeutet, dass Sie wirklich ein benanntes Objekt vom Typ std::function benötigen.

Was im zweiten Fall passiert: Sie erstellen ein temporäres Lambda, das an die Funktion übergeben wird. Da die Funktion eine std::function erwartet, wird eine temporäre std::function aus dem Lambda erstellt. Beide werden am Ende der Linie zerstört werden. Daher haben Sie jetzt einen Zeiger auf ein bereits zerstörtes temporäres Objekt, was bedeutet, dass Sie, wenn Sie versuchen, das Objekt zu verwenden, das Sie als Objekt verwenden, in ein undefiniertes Verhaltensgebiet gelangen.

+0

Nun, das ist nicht unbedingt wahr für Lambdas, da sie in "echte" Funktionen umgeschrieben werden, so dass sie im Speicher bleiben, unter der gleichen Adresse . –

+0

Am Ende des enthaltenden _full Ausdruck_ tatsächlich. In diesem Fall ist das ungefähr diese Linie. – sehe

+0

@BarnabasSzabolcs: Sind Sie sicher? Ein Lambda kann implizit in einen Funktionszeiger umgewandelt werden (also ist die Funktion irgendwo, so viel ist wahr), aber wird das Lambda tatsächlich so umgeschrieben, dass es mit diesem Funktionszeiger identisch ist? scheint verschwenderisch in Bezug auf verpasste Inlinemöglichkeiten. Wie auch immer, es spielt keine Rolle, denn der wichtige Punkt ist, dass die "std :: function" zerstört ist – Grizzly

1

Es ist in Ordnung für statesless lambdas. Stateless-Lambdas haben eine implizite Umwandlung in den Funktions-Zeigertyp.

Auch gibt es immer eine implizite Konvertierung in std :: Funktion <> unabhängig von der tatsächlichen aufrufbaren Typ.

Es gibt Probleme mit einem Zeiger auf Provisorien, obwohl. Das hatte ich beim ersten Lesen des Codes nicht bemerkt.

Das hat natürlich nichts mit std :: function zu tun.

+0

Es ist in Ordnung, einen Zeiger auf eine temporäre 'std :: function' zu speichern, wenn das in der' function' eingeschlossene Objekt ein Funktionspointer ist? – Grizzly

+0

Nein. Zeiger auf Provisorien sind immer schlecht. Weißt du, das hat nichts mit std :: function <> wirklich zu tun. – sehe

+0

** Ich weiß das, aber deine Antwort scheint zu implizieren, dass es (also möchtest du vielleicht den Wortlaut ändern;)) – Grizzly

0

Temporäre Objekte werden am Ende der Anweisung zerstört, in der sie erstellt wurden. Sie haben dies gerade mit einem Lambda demonstriert.

Die Std-Funktion, die Sie das erste Mal übergeben, ist keine temporäre, also dauert es so lange wie die Variable. Es speichert tatsächlich eine Kopie des temporären.

Um Ihre Lamdas mehr als eine Linie dauern zu lassen, müssen Sie sie irgendwo speichern und ihre Lebensdauer bestimmt haben.

Es gibt viele Möglichkeiten, dies zu tun. Std-Funktion ist eine Art löschungsbasierten Weg, um den Speicher zu tun - dh, speichern Sie eine Std-Funktion anstelle eines Zeigers auf eine Std-Funktion. Ändern Sie den Typ von pointer zu einem Nicht-Zeiger (und nicht const), loswerden &, wenn Sie zuweisen, und rufen Sie es direkt auf.

Im Allgemeinen vermeiden und speichern Sie die Adresse der const Referenzfunktionsparameter als temporäre Bindung an sie, es sei denn, Sie fangen die Rvalue Referenzmöglichkeit in einer anderen Überladung.

+0

Eine Möglichkeit, das Lambda zu speichern, ohne den Laufzeit-Overhead des Typs stor :: function zu haben? –

+0

@joaorafael In einer Funktion wird der Typ automatisch abgeleitet. In einer Template-Klasse, die den Aufruf umhüllt, kann ein funktionsbasierter Ersteller den Lambda-Typ ableiten und an die Klasse übergeben, so dass die Klasse ein nicht gelöschtes Lambda speichern kann. Ein philosophischer Ansatz kann Sie fragen, ob Sie wissen, dass dies ein Leistungsengpass ist und ob Ihre Bemühungen nicht anderswo besser eingesetzt werden. Der Code, der Lambda speichert und verwendet, muss eine Funktion des Lambdas-Typs sein, wenn Sie den Overhead des Löschens von Typ vermeiden möchten. – Yakk

Verwandte Themen