2016-05-02 19 views
5

konstruiert Ich bin ein wenig verwirrt, wie std::function bei einem gegebenen Lambda aufgebaut ist. Der Konstruktor std::function ist here aufgeführt. Welches wird tatsächlich verwendet, um ein Lambda zu erfassen? Ist es template< class F > function(F f);? Sieht so aus, als könnte ich std::function nicht mit einem Lambda konstruieren, das nicht kopierfähige Objekte erfasst. Warum ist dies für die Lambda-Erfassung notwendig?Wie ist std :: function für ein Lambda

// fu is an object of type std::future 
std::function f = [future=std::move(fu)]() {...} // compile error 

// foo is an object of type int 
std::function f = [foo=std::move(foo)]() {...} // compile ok 
+0

ähnliche Frage: http://stackoverflow.com/questions/25421346/how- to-create-a-stdfunction-from-a-move-capturen-lambda-expression – marcinj

Antwort

3

Die kurze Antwort ist, dass die Standardzustände nur kopierbare Funktionsobjekte in einer std::function gespeichert werden können. Das ist unbefriedigend: warum?

std::function ist ein kopierbarer Typ.

Der Standard besagt, dass beim Kopieren auch der Inhalt kopiert wird.

"Aber", sagst du, "ich kopiere es nie. Warum sollte es kopiert werden?" Eine Instanz std::function Datensätze wie seinen Inhalt zu kopieren, auch wenn es nie tut. Es verwendet typischerweise eine Technik, die als Typlöschung bekannt ist.

ist hier ein Spielzeug Beispiel:

struct invoke_later { 
    struct i_impl { 
    virtual ~i_impl() {} 
    virtual void invoke() const = 0; 
    virtual std::unique_ptr<i_impl> clone() const = 0; 
    }; 
    template<class T> 
    struct impl:i_impl { 
    T t; 
    ~impl() = default; 
    void invoke() const override { 
     t(); 
    } 
    impl(T&& tin):t(std::move(tin)) {} 
    impl(T const& tin):t(tin) {} 
    virtual std::unique_ptr<i_impl> clone() const { 
     return std::make_unique<impl>(t); 
    }; 
    }; 
    std::unique_ptr<i_impl> pimpl; 

    template<class T, 
    // SFINAE suppress using this ctor instead of copy/move ctors: 
    std::enable_if_t< !std::is_same<std::decay_t<T>, invoke_later>{}, int>* =0 
    > 
    invoke_later(T&& t): 
    pimpl(std::make_unique<impl<std::decay_t<T>>(std::forward<T>(t))) 
    {} 

    invoke_later(invoke_later&&)=default; 
    invoke_later(invoke_later const&o): 
    pimpl(o.pimpl?o.pimpl->clone():std::unique_ptr<i_impl>{}) 
    {} 

    ~invoke_later() = default; 

    // assignment goes here 

    void operator() const { 
    pimpl->invoke(); 
    } 
    explicit operator bool() const { return !!pimpl; } 
}; 

die oben ist ein Spielzeug Beispiel eines std::function<void()>.

Der Kopiervorgang ist gespeichert in der ->clone() Methode der pimpl. Es muss kompiliert werden, auch wenn Sie es nie nennen.

Die Autoren der std Spezifikation, wo bewusst, die oben genannten Technik, und wusste seine Grenzen, und wollte std::function s einfach implementiert werden, damit es implementiert werden. Darüber hinaus wollten sie einfache Operationen auf der std::function auf vorhersehbare Weise verhalten: mit nicht kopierbaren Inhalten, was sollte das Kopieren std::function tun?

Hinweis: Sie können dieses Problem umgehen, indem Sie Ihren Status in eine shared_ptr umhüllen. Copys von std::function speichern einfach gemeinsame Referenzen anstelle von Kopien auf Ihren Status.

template<class F> 
auto shared_state(F&& f) { 
    return [pf = std::make_shared<std::decay_t<F>>(std::forward<F>(f))] 
    (auto&&... args)->decltype(auto) { 
     return (*pf)(decltype(args)(args)...); 
    }; 
} 

jetzt:

std::function<Sig> f = shared_state([future=std::move(fu)]() {...}); 

kompilieren und zu arbeiten.

Ein alternativer Ansatz besteht darin, einen nicht kopierbaren std::function zu erstellen und diesen anstelle von std::function zu verwenden.

Schließlich WHE mit future arbeiten, ein shared_future ist ein kopierbar future Typ und kann billiger sein als eine shared_state tun:

std::function<void()> f = [fu=fu.share()]{ /* code */ }; 
+0

Für 'future's insbesondere ist' .share() 'möglicherweise billiger. –

+0

@ t.c. guter Punkt: einen Absatz hinzugefügt. – Yakk

1

Ein Lambda, die einen Zug-only-Objekt durch einen Wert erfasst wird, sich nur bewegen-, was Sinn macht, da es das Objekt enthält.

Aber std::function muss copy-constructible und copy-assignable sein, was bedeutet, dass es nur kopierbare Objekte enthalten kann.