2016-08-08 10 views
4

Ich habe eine Klasse, die eine std::function als Parameter, den ich eine Lambda-Funktion zuweisen. Es funktioniert im Konstruktor, aber danach funktioniert es nicht mehr. Der Debugger sagt f ist "leer" nach dem Ausführen der ersten Zeile. Warum?Std :: Funktionen und Lambda-Funktion übergeben

#include <iostream> 
#include <string> 
#include <functional> 

typedef std::function<void(std::string)> const& fn; 

class TestClass 
{ 
public: 
    TestClass(fn _f) : f(_f) { F(); } 
    void F() { f("hello"); }; 

private: 
    fn f; 
}; 


int main() 
{ 
    TestClass t([](std::string str) {std::cout << str << std::endl; }); 

    t.F(); 

    return 0; 
} 

t.F() Aufruf verursacht einen Fehler. Warum?

Ich kann es lösen, indem es auf die folgende Veränderung:

int main() 
{ 
    fn __f = [](std::string str) {std::cout << str << std::endl; }; 
    TestClass t(__f); 

    t.F(); 

    return 0; 
} 

aber auch hier funktioniert das nicht, wenn ich fn-auto ändern!

int main() 
{ 
    auto __f = [](std::string str) {std::cout << str << std::endl; }; 
    TestClass t(__f); 

    t.F(); 

    return 0; 
} 

Was ist die Erklärung warum dies passiert?

+7

Sie binden eine Referenz an eine lokale Variable, die bald darauf zerstört wird und die Referenz nicht mehr freigibt. Jede Verwendung von "f" weist danach undefiniertes Verhalten auf. Das Klassenmitglied sollte vom Typ 'std :: function <...>' sein, nicht 'std :: function <...> &'. –

+2

Bitte vermeiden Sie die Vorsilbe mit Unterstrichen. doppelte Unterstrichnamen sind für die Implementierung und an bestimmten Stellen reserviert. So sind auch einzelne Unterstriche. – NathanOliver

+0

@IgorTandetnik Dies erklärt nicht, warum der letzte Code mit 'auto' nicht wotk – alexeykuzmin0

Antwort

5

Beachten Sie, dass (1) fn definiert ist als Referenz (zu const); (2) Lambda und std::function sind nicht vom selben Typ; (3) Sie können keine Referenz direkt an ein Objekt mit einem anderen Typ binden.

Für den ersten Fall

TestClass t([](std::string str) {std::cout << str << std::endl; }); 
t.F(); 

Eine temporäre Lambda erstellt wird und dann zu std::function umgewandelt, die zu einem vorübergehend ist. Das temporäre std::function ist an den Parameter _f des Konstruktors gebunden und an Member f gebunden. Das temporäre wird nach dieser Anweisung zerstört, dann f wird baumeln, wenn t.F(); es fehlschlägt.

Für den 2. Fall

fn __f = [](std::string str) {std::cout << str << std::endl; }; 
TestClass t(__f); 
t.F(); 

Eine temporäre lambda erstellt wird und dann zur Referenz gebunden (auf const). Dann ist seine Lebensdauer auf die Lebensdauer der Referenz __f verlängert, so dass der Code in Ordnung ist.

Für den dritten Fall

auto __f = [](std::string str) {std::cout << str << std::endl; }; 
TestClass t(__f); 
t.F(); 

Lambda erzeugt und dann zu std::function umgewandelt, die eine vorübergehende ist. Das temporäre std::function ist an den Parameter _f des Konstruktors gebunden und an Member f gebunden. Das temporäre wird nach dieser Anweisung zerstört, dann f wird baumeln, wenn t.F(); es fehlschlägt.


(1) Sie fn als Nicht-Referenz wie typedef std::function<void(std::string)> fn; erklären könnte, wird dann std::function kopiert werden und jeder Fall würde gut funktionieren.
(2) Verwenden Sie keine Namen mit doppeltem Unterstrich beginnen, sie sind in C++ reserviert.

3
typedef std::function<void(std::string)> const& fn; 

Dies ist kein std::function, ist es ein Verweis auf eine std::function.

TestClass(fn _f) : f(_f) { F(); } 
fn f; 

Hier nehmen Sie eine const& einen std::function und binden sie an einen anderen const& zu einem std::function. Die F() im Hauptteil des Konstruktors funktioniert, da die Referenz mindestens so lange gültig ist wie der Konstruktor.

TestClass t([](std::string str) {std::cout << str << std::endl; }); 

Dies schafft eine std::function vorübergehend aus dem Lambda erstellt. Diese temporäre dauert so lange wie die aktuelle Zeile (bis ;).

Dann wird die temporäre std::function verworfen.

Da TestClass die std::function von const& übernimmt, verlängert es die Lebensdauer der Provisorien nicht.

Also nach der Linie, jeder Anruf der std::function const& ist undefiniertes Verhalten, das Sie in dem Anruf zu .F() später sehen.

Dies bezieht sich auf die Lebensdauer verlängern. Die temporäre std::function aus dem Lambda hat seine Lebensdauer auf die Lebensdauer der __f Variable erweitert.

Nebenbei bemerkt, diese Zeile macht auch Ihr Programm schlecht gebildet, keine Diagnose erforderlich, indem Sie eine Variable mit einem doppelten Unterstrich haben. Solche Bezeichner sind für die Implementierung des Compilers reserviert, Sie dürfen sie nicht erstellen.

Wir übergeben dann diese Referenz (bezogen auf eine lebenslange erweiterte temporäre), und alles funktioniert.

auto __f = [](std::string str) {std::cout << str << std::endl; }; 

Dies schafft eine Variable __f (siehe oben, in Verruf), die eine Lambda ist.

Ein Lambda ist kein std::function.Ein std::function kann implizit aus einem Lambda erzeugt werden.

TestClass t(__f); 

Dies erzeugt eine temporäre std::function aus dem Lambda, leitet sie an den TestClass Konstruktor, dann die temporäre zerstört.

Nach dieser Zeile endet der Aufruf von .F() mit einer dangling-Referenz und einem undefinierten Verhalten.

Ihr Kernproblem kann sein, dass Sie denken, dass ein Lambda ein std::function ist. Es ist nicht. A std::function kann ein Lambda speichern.

Ihr zweites Problem ist Typdefing etwas als const&, die fast immer eine wirklich dumme Idee ist. Referenzen verhalten sich grundlegend anders als Werte.

Ihr drittes Problem ist der doppelte Unterstrich in Ihren Variablennamen. (Oder ein Bezeichner, der mit einem _ beginnt, gefolgt von einem Großbuchstaben).

Wenn Sie wissen möchten, wie std::function funktioniert und was es ist, gibt es viele gute SO Beiträge zum Thema mit verschiedenen Ebenen von technischen Details.

Verwandte Themen