Verallgemeinerte Lambda-Erfassung in C ++ 14
In C ++ 14 haben wir die sogenannte generalisierte Lambda-Erfassung . Dies ermöglicht die Bewegungserfassung. Folgendes ist in C ++ 14 legal:
using namespace std;
// a unique_ptr is move-only
auto u = make_unique<some_type>( some, parameters );
// move the unique_ptr into the lambda
go.run( [ u{move(u)} ] { do_something_with( u ); } );
Es ist jedoch viel allgemeiner in dem Sinne, dass erfasste Variablen folgendermaßen initialisiert werden können:
auto lambda = [value = 0] mutable { return ++value; };
In C ++ 11 ist dies noch nicht möglich, aber mit einigen Tricks, die Hilfstypen beinhalten. Glücklicherweise implementiert der Clang 3.4-Compiler diese großartige Funktion bereits. Der Compiler wird im Dezember 2013 oder Januar 2014 veröffentlicht, wenn das aktuelle Release-Tempo beibehalten wird.
UPDATE: Der Clang 3.4-Compiler wurde am 6. Januar 2014 mit dieser Funktion veröffentlicht.
Eine Problemumgehung für die Bewegungserfassung
Hier ist eine Implementierung einer Hilfsfunktion, make_rref
die bei der Erfassung künstlicher Bewegungen hilft
#include <cassert>
#include <memory>
#include <utility>
template <typename T>
struct rref_impl
{
rref_impl() = delete;
rref_impl( T && x ) : x{std::move(x)} {}
rref_impl( rref_impl & other )
: x{std::move(other.x)}, isCopied{true}
{
assert( other.isCopied == false );
}
rref_impl( rref_impl && other )
: x{std::move(other.x)}, isCopied{std::move(other.isCopied)}
{
}
rref_impl & operator=( rref_impl other ) = delete;
T && move()
{
return std::move(x);
}
private:
T x;
bool isCopied = false;
};
template<typename T> rref_impl<T> make_rref( T && x )
{
return rref_impl<T>{ std::move(x) };
}
Und hier ist ein Testfall für diese Funktion, die auf meinem gcc 4.7.3 erfolgreich ausgeführt wurde.
int main()
{
std::unique_ptr<int> p{new int(0)};
auto rref = make_rref( std::move(p) );
auto lambda =
[rref]() mutable -> std::unique_ptr<int> { return rref.move(); };
assert( lambda() );
assert( !lambda() );
}
Der Nachteil hierbei ist, dass lambda
es kopierbar ist und beim Kopieren die Behauptung im Kopierkonstruktor von rref_impl
fehlschlägt, was zu einem Laufzeitfehler führt. Das Folgende ist möglicherweise eine bessere und noch allgemeinere Lösung, da der Compiler den Fehler abfängt.
Emulieren der verallgemeinerten Lambda-Erfassung in C ++ 11
Hier ist eine weitere Idee, wie die generalisierte Lambda-Erfassung implementiert werden kann. Die Verwendung der Funktion capture()
(deren Implementierung weiter unten zu finden ist) ist wie folgt:
#include <cassert>
#include <memory>
int main()
{
std::unique_ptr<int> p{new int(0)};
auto lambda = capture( std::move(p),
[]( std::unique_ptr<int> & p ) { return std::move(p); } );
assert( lambda() );
assert( !lambda() );
}
Hier lambda
ist ein Funktor Objekt (fast eine echte Lambda) , die eingefangen hat , std::move(p)
wie es übergeben wird capture()
. Das zweite Argument von capture
ist ein Lambda, das die erfasste Variable als Argument verwendet. Wenn lambda
es als Funktionsobjekt verwendet wird, werden alle an es übergebenen Argumente als Argumente nach der erfassten Variablen an das interne Lambda weitergeleitet. (In unserem Fall sind keine weiteren Argumente zu übermitteln). Im Wesentlichen geschieht das Gleiche wie in der vorherigen Lösung. So capture
wird implementiert:
#include <utility>
template <typename T, typename F>
class capture_impl
{
T x;
F f;
public:
capture_impl( T && x, F && f )
: x{std::forward<T>(x)}, f{std::forward<F>(f)}
{}
template <typename ...Ts> auto operator()( Ts&&...args )
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
template <typename ...Ts> auto operator()( Ts&&...args ) const
-> decltype(f( x, std::forward<Ts>(args)... ))
{
return f( x, std::forward<Ts>(args)... );
}
};
template <typename T, typename F>
capture_impl<T,F> capture( T && x, F && f )
{
return capture_impl<T,F>(
std::forward<T>(x), std::forward<F>(f) );
}
Diese zweite Lösung ist ebenfalls sauberer, da sie das Kopieren des Lambda deaktiviert, wenn der erfasste Typ nicht kopierbar ist. In der ersten Lösung kann das nur zur Laufzeit mit einem überprüft werden assert()
.
moveCapture
Wrapper, um sie als Argumente zu übergeben (diese Methode wird oben und in Capn'Proto, einer Bibliothek des Erstellers von Protobuffs, verwendet), oder akzeptieren Sie einfach, dass Sie Compiler benötigen, die dies unterstützen: PSie können auch
std::bind
Folgendes verwendenunique_ptr
:quelle
unique_ptr
rvalue-Referenz nicht an eine binden kannint *
.myPointer
in diesem Fall). Daher wird der obige Code in VS2013 nicht kompiliert. In GCC 4.8 funktioniert es jedoch einwandfrei.Sie können das meiste erreichen, was Sie wollen
std::bind
, wie folgt:Der Trick dabei ist, dass wir Ihr Nur-Verschieben-Objekt nicht in der Erfassungsliste erfassen, sondern es zu einem Argument machen und dann eine Teilanwendung über verwenden
std::bind
, um es verschwinden zu lassen. Beachten Sie, dass das Lambda es als Referenz verwendet , da es tatsächlich im Bindungsobjekt gespeichert ist. Ich habe auch Code hinzugefügt, der in das tatsächlich bewegliche Objekt schreibt , da Sie dies möglicherweise tun möchten.In C ++ 14 können Sie mit diesem Code die verallgemeinerte Lambda-Erfassung verwenden, um dieselben Ziele zu erreichen:
Aber dieser Code kauft Ihnen nichts, was Sie in C ++ 11 nicht über hatten
std::bind
. (In einigen Situationen ist die allgemeine Lambda-Erfassung leistungsfähiger, in diesem Fall jedoch nicht.)Jetzt gibt es nur noch ein Problem. Sie wollten diese Funktion in eine setzen
std::function
, aber diese Klasse erfordert, dass die Funktion CopyConstructible ist , aber nicht, es ist nur MoveConstructible, weil sie eine speichert,std::unique_ptr
die nicht CopyConstructible ist .Sie müssen das Problem mit der Wrapper-Klasse und einer anderen Indirektionsebene umgehen, aber vielleicht brauchen Sie das überhaupt nicht
std::function
. Abhängig von Ihren Anforderungen können Sie möglicherweise verwendenstd::packaged_task
; Es würde den gleichen Job machen wiestd::function
, aber es erfordert nicht, dass die Funktion kopierbar ist, sondern nur beweglich (in ähnlicher Weisestd::packaged_task
nur beweglich). Der Nachteil ist, dass Sie es nur einmal aufrufen können, da es in Verbindung mit std :: future verwendet werden soll.Hier ist ein kurzes Programm, das all diese Konzepte zeigt.
Ich habe das oben genannte Programm auf Coliru gestellt , damit Sie mit dem Code spielen können.
Hier sind einige typische Ausgaben ...
Sie sehen, dass Heap-Speicherorte wiederverwendet werden, was zeigt, dass der
std::unique_ptr
ordnungsgemäß funktioniert. Sie sehen auch, wie sich die Funktion selbst bewegt, wenn wir sie in einem Wrapper aufbewahren, dem wir den Feed zuführenstd::function
.Wenn wir zu using wechseln,
std::packaged_task
wird es der letzte TeilWir sehen also, dass die Funktion verschoben wurde, aber anstatt auf den Heap verschoben zu werden, befindet sie sich innerhalb
std::packaged_task
des Stapels.Hoffe das hilft!
quelle
Spät, aber da einige Leute (einschließlich ich) immer noch auf c ++ 11 stecken:
Um ehrlich zu sein, mag ich keine der veröffentlichten Lösungen wirklich. Ich bin mir sicher, dass sie funktionieren werden, aber sie erfordern eine Menge zusätzlicher Dinge und / oder kryptischer
std::bind
Syntax ... und ich denke nicht, dass sich die Mühe für eine solche temporäre Lösung lohnt, die beim Upgrade auf c ++ ohnehin überarbeitet wird = 14. Ich denke, die beste Lösung besteht darin, die Verschiebungserfassung für c ++ 11 vollständig zu vermeiden.Normalerweise ist die einfachste und am besten lesbare Lösung die Verwendung
std::shared_ptr
, die kopierbar sind und so das Verschieben vollständig vermeidbar ist. Nachteil ist, dass es etwas weniger effizient ist, aber in vielen Fällen ist Effizienz nicht so wichtig..
Wenn der sehr seltene Fall auftritt, ist dies wirklich obligatorisch
move
den Zeiger (z. B. wenn Sie einen Zeiger aufgrund der langen Löschdauer explizit in einem separaten Thread löschen möchten oder die Leistung absolut entscheidend ist), ist dies so ziemlich der einzige Fall, in dem ich ihn noch verwende Rohzeiger in c ++ 11. Diese sind natürlich auch kopierbar.Normalerweise markiere ich diese seltenen Fälle mit einem,
//FIXME:
um sicherzustellen, dass sie nach dem Upgrade auf c ++ 14 überarbeitet werden.Ja, rohe Zeiger sind heutzutage ziemlich verpönt (und das nicht ohne Grund), aber ich denke wirklich, dass sie in diesen seltenen (und vorübergehenden!) Fällen die beste Lösung sind.
quelle
Ich habe mir diese Antworten angesehen, aber ich fand es schwierig, sie zu lesen und zu verstehen. Also habe ich eine Klasse erstellt, die stattdessen kopiert wurde. Auf diese Weise wird explizit erklärt, was es tut.
Die
move_with_copy_ctor
Klasse und ihre Hilfsfunktion funktionierenmake_movable()
mit jedem beweglichen, aber nicht kopierbaren Objekt. Verwenden Sie die Taste, um Zugriff auf das umschlossene Objekt zu erhaltenoperator()()
.Erwartete Ausgabe:
Nun, die Zeigeradresse kann variieren. ;)
Demo
quelle
Dies scheint auf gcc4.8 zu funktionieren
quelle