Wie fange ich durch Verschieben (auch als R-Wert-Referenz bezeichnet) in einem C++ 11-Lambda auf?
Ich versuche so etwas zu schreiben:
std::unique_ptr<int> myPointer(new int);
std::function<void(void)> example = [std::move(myPointer)]{
(*myPointer) = 4;
};
In C++ 14 haben wir die sogenannte generalisierte Lambda-Erfassung . Dies ermöglicht die Bewegungserfassung. Das Folgende wird in C++ 14 legaler Code sein:
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 mit so etwas initialisiert werden können:
auto lambda = [value = 0] mutable { return ++value; };
In C++ 11 ist dies noch nicht möglich, aber bei einigen Tricks, die Hilfstypen beinhalten. Glücklicherweise implementiert der Clang 3.4-Compiler dieses fantastische Feature bereits. Der Compiler wird im Dezember 2013 oder Januar 2014 veröffentlicht, wenn das Recent Release Pace beibehalten wird.
UPDATE: Der Clang 3.4 Compiler wurde am 6. Januar 2014 mit der besagten Funktion veröffentlicht.
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
kopierbar ist und die Assertion im Kopierkonstruktor von rref_impl
beim Kopieren fehlschlägt, was zu einem Laufzeitfehler führt. Das Folgende könnte eine bessere und noch allgemeinere Lösung sein, da der Compiler den Fehler abfängt.
Hier ist eine weitere Idee, wie Sie die allgemeine Lambda-Erfassung implementieren können. Die Funktion capture()
(deren Implementierung weiter unten zu finden ist) sieht folgendermaßen aus:
#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 ist lambda
ein Funktionsobjekt (fast ein echtes Lambda), das std::move(p)
erfasst hat, als es an capture()
übergeben wird. Das zweite Argument von capture
ist ein Lambda, das die erfasste Variable als Argument verwendet. Wenn lambda
als Funktionsobjekt verwendet wird, werden alle an ihn übergebenen Argumente als Argumente nach der erfassten Variablen an das interne Lambda weitergeleitet. (In unserem Fall gibt es keine weiteren Argumente, die weitergeleitet werden müssen). Im Wesentlichen passiert dasselbe wie in der vorherigen Lösung. So wird capture
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 auch sauberer, da das Kopieren des Lambda deaktiviert wird, wenn der erfasste Typ nicht kopierbar ist. In der ersten Lösung kann dies nur zur Laufzeit mit einer assert()
überprüft werden.
Sie können auch std::bind
verwenden, um den unique_ptr
zu erfassen:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
Das meiste, was Sie wollen, können Sie mit std::bind
wie folgt erreichen:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = std::bind([](std::unique_ptr<int>& myPointerArg){
*myPointerArg = 4;
myPointerArg.reset(new int{237});
}, std::move(myPointer));
Der Trick hier besteht darin, dass Sie Ihr Move-Only-Objekt nicht in der Captures-Liste erfassen, sondern als Argument definieren und dann die Teilanwendung über std::bind
verwenden, um es verschwinden zu lassen. Beachten Sie, dass das Lambda durch Referenz benötigt, da es tatsächlich im Bind-Objekt gespeichert ist. Ich habe auch Code hinzugefügt, der schreibt zum eigentlichen beweglichen Objekt, weil Sie dies vielleicht tun möchten.
In C++ 14 können Sie die generalisierte Lambda-Capture verwenden, um mit diesem Code die gleichen Ziele zu erreichen:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Dieser Code kauft Sie jedoch nicht über C++ 11 über std::bind
. (Es gibt einige Situationen, in denen die allgemeine Lambda-Erfassung leistungsfähiger ist, in diesem Fall jedoch nicht.)
Jetzt gibt es nur ein Problem. Sie wollten diese Funktion in einen std::function
einfügen, aber diese Klasse erfordert, dass die Funktion CopyConstructible ist. Dies ist jedoch nicht der Fall - es ist nur MoveConstructible , weil sie einen std::unique_ptr
speichert, der nicht vorhanden ist CopyConstructible .
Sie können das Problem mit der Wrapper-Klasse und einer anderen Ebene der Dereferenzierung umgehen, benötigen aber möglicherweise gar keine std::function
. Je nach Ihren Bedürfnissen können Sie std::packaged_task
verwenden. es würde die gleiche Arbeit wie std::function
ausführen, aber die Funktion muss nicht kopierbar sein, sondern nur beweglich sein (ähnlich ist std::packaged_task
nur beweglich). Der Nachteil ist, dass es nur einmal aufgerufen werden kann, da es für die Verwendung mit std :: future vorgesehen ist.
Hier ist ein kurzes Programm, das alle diese Konzepte zeigt.
#include <functional> // for std::bind
#include <memory> // for std::unique_ptr
#include <utility> // for std::move
#include <future> // for std::packaged_task
#include <iostream> // printing
#include <type_traits> // for std::result_of
#include <cstddef>
void showPtr(const char* name, const std::unique_ptr<size_t>& ptr)
{
std::cout << "- &" << name << " = " << &ptr << ", " << name << ".get() = "
<< ptr.get();
if (ptr)
std::cout << ", *" << name << " = " << *ptr;
std::cout << std::endl;
}
// If you must use std::function, but your function is MoveConstructable
// but not CopyConstructable, you can wrap it in a shared pointer.
template <typename F>
class shared_function : public std::shared_ptr<F> {
public:
using std::shared_ptr<F>::shared_ptr;
template <typename ...Args>
auto operator()(Args&&...args) const
-> typename std::result_of<F(Args...)>::type
{
return (*(this->get()))(std::forward<Args>(args)...);
}
};
template <typename F>
shared_function<F> make_shared_fn(F&& f)
{
return shared_function<F>{
new typename std::remove_reference<F>::type{std::forward<F>(f)}};
}
int main()
{
std::unique_ptr<size_t> myPointer(new size_t{42});
showPtr("myPointer", myPointer);
std::cout << "Creating lambda\n";
#if __cplusplus == 201103L // C++ 11
// Use std::bind
auto lambda = std::bind([](std::unique_ptr<size_t>& myPointerArg){
showPtr("myPointerArg", myPointerArg);
*myPointerArg *= 56; // Reads our movable thing
showPtr("myPointerArg", myPointerArg);
myPointerArg.reset(new size_t{*myPointerArg * 237}); // Writes it
showPtr("myPointerArg", myPointerArg);
}, std::move(myPointer));
#Elif __cplusplus > 201103L // C++14
// Use generalized capture
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
showPtr("myPointerCapture", myPointerCapture);
*myPointerCapture *= 56;
showPtr("myPointerCapture", myPointerCapture);
myPointerCapture.reset(new size_t{*myPointerCapture * 237});
showPtr("myPointerCapture", myPointerCapture);
};
#else
#error We need C++11
#endif
showPtr("myPointer", myPointer);
std::cout << "#1: lambda()\n";
lambda();
std::cout << "#2: lambda()\n";
lambda();
std::cout << "#3: lambda()\n";
lambda();
#if ONLY_NEED_TO_CALL_ONCE
// In some situations, std::packaged_task is an alternative to
// std::function, e.g., if you only plan to call it once. Otherwise
// you need to write your own wrapper to handle move-only function.
std::cout << "Moving to std::packaged_task\n";
std::packaged_task<void()> f{std::move(lambda)};
std::cout << "#4: f()\n";
f();
#else
// Otherwise, we need to turn our move-only function into one that can
// be copied freely. There is no guarantee that it'll only be copied
// once, so we resort to using a shared pointer.
std::cout << "Moving to std::function\n";
std::function<void()> f{make_shared_fn(std::move(lambda))};
std::cout << "#4: f()\n";
f();
std::cout << "#5: f()\n";
f();
std::cout << "#6: f()\n";
f();
#endif
}
Ich habe ein Programm auf Coliru gestellt, damit Sie mit dem Code laufen und spielen können.
Hier ist eine typische Ausgabe ...
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x7ae3cfd0, *myPointer = 42
Creating lambda
- &myPointer = 0xbfffe5c0, myPointer.get() = 0x0
#1: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 42
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 2352
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
#2: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 557424
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 31215744
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
#3: lambda()
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 3103164032
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfd0, *myPointerArg = 1978493952
- &myPointerArg = 0xbfffe5b4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
Moving to std::function
#4: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
#5: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2967666688
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
#6: f()
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3257335808
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 2022178816
- &myPointerArg = 0x7ae3cfd4, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2515009536
Sie sehen, dass Heap-Standorte wiederverwendet werden, was zeigt, dass der std::unique_ptr
ordnungsgemäß funktioniert. Sie sehen auch, dass sich die Funktion selbst bewegt, wenn wir sie in einem Wrapper verstauen, den wir an std::function
übergeben.
Wenn wir zu std::packaged_task
wechseln, wird der letzte Teil
Moving to std::packaged_task
#4: f()
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 751631360
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3cfe0, *myPointerArg = 3436650496
- &myPointerArg = 0xbfffe590, myPointerArg.get() = 0x7ae3d000, *myPointerArg = 2737348608
wir sehen also, dass die Funktion verschoben wurde, aber anstatt auf den Heap verschoben zu werden, ist sie im std::packaged_task
, der sich auf dem Stapel befindet.
Hoffe das hilft!
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 deutlich, was es tut.
#include <iostream>
#include <memory>
#include <utility>
#include <type_traits>
#include <functional>
namespace detail
{
enum selection_enabler { enabled };
}
#define ENABLE_IF(...) std::enable_if_t<(__VA_ARGS__), ::detail::selection_enabler> \
= ::detail::enabled
// This allows forwarding an object using the copy constructor
template <typename T>
struct move_with_copy_ctor
{
// forwarding constructor
template <typename T2
// Disable constructor for it's own type, since it would
// conflict with the copy constructor.
, ENABLE_IF(
!std::is_same<std::remove_reference_t<T2>, move_with_copy_ctor>::value
)
>
move_with_copy_ctor(T2&& object)
: wrapped_object(std::forward<T2>(object))
{
}
// move object to wrapped_object
move_with_copy_ctor(T&& object)
: wrapped_object(std::move(object))
{
}
// Copy constructor being used as move constructor.
move_with_copy_ctor(move_with_copy_ctor const& object)
{
std::swap(wrapped_object, const_cast<move_with_copy_ctor&>(object).wrapped_object);
}
// access to wrapped object
T& operator()() { return wrapped_object; }
private:
T wrapped_object;
};
template <typename T>
move_with_copy_ctor<T> make_movable(T&& object)
{
return{ std::forward<T>(object) };
}
auto fn1()
{
std::unique_ptr<int, std::function<void(int*)>> x(new int(1)
, [](int * x)
{
std::cout << "Destroying " << x << std::endl;
delete x;
});
return [y = make_movable(std::move(x))]() mutable {
std::cout << "value: " << *y() << std::endl;
return;
};
}
int main()
{
{
auto x = fn1();
x();
std::cout << "object still not deleted\n";
x();
}
std::cout << "object was deleted\n";
}
Die Klasse move_with_copy_ctor
und ihre Hilfsfunktion make_movable()
funktionieren mit jedem beweglichen, aber nicht kopierbaren Objekt. Verwenden Sie die Funktion operator()()
, um Zugriff auf das umschlossene Objekt zu erhalten.
Erwartete Ausgabe:
Wert: 1 Objekt noch nicht gelöscht Wert: 1 Zerstören von 000000DFDD172280 Objekt wurde gelöscht
Nun, die Zeigeradresse kann variieren. ;)
Dies scheint bei gcc4.8 zu funktionieren
#include <memory>
#include <iostream>
struct Foo {};
void bar(std::unique_ptr<Foo> p) {
std::cout << "bar\n";
}
int main() {
std::unique_ptr<Foo> p(new Foo);
auto f = [ptr = std::move(p)]() mutable {
bar(std::move(ptr));
};
f();
return 0;
}
Spät, aber da einige Leute (einschließlich mir) immer noch auf C++ 11 stecken:
Um ehrlich zu sein, mag ich keine der veröffentlichten Lösungen. Ich bin mir sicher, dass sie funktionieren werden, aber sie benötigen eine Menge zusätzliches Material und/oder eine kryptische 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 umgestaltet wird > = 14. Ich denke, die beste Lösung ist es, das Capture für C++ 11 vollständig zu verschieben.
Normalerweise ist die einfachste und am besten lesbare Lösung die Verwendung von std::shared_ptr
, die kopierbar sind und daher völlig umgangen werden können. Nachteil ist, dass es etwas weniger effizient ist, aber in vielen Fällen ist Effizienz nicht so wichtig.
// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);
// convert/move the unique ptr into a shared ptr
std::shared_ptr<int> mySharedPointer( std::move(myPointer) );
std::function<void(void)> = [mySharedPointer](){
*mySharedPointer = 4;
};
// at end of scope the original mySharedPointer is destroyed,
// but the copy still lives in the lambda capture.
.
Wenn der sehr seltene Fall eintritt, dass es wirklich zwingend erforderlich ist, den Zeiger move
zu verwenden (z. B. möchten Sie einen Zeiger in einem separaten Thread wegen längerer Löschdauer explizit löschen, oder die Leistung ist absolut entscheidend), ist dies der einzige Fall, in dem ich immer noch Verwenden Sie rohe Zeiger in C++ 11. Diese sind natürlich auch kopierbar.
Normalerweise markiere ich diese seltenen Fälle mit einem //FIXME:
, um sicherzustellen, dass er nach einem Upgrade auf c ++ 14 umgestaltet wird.
// myPointer could be a parameter or something
std::unique_ptr<int> myPointer(new int);
//FIXME:c++11 upgrade to new move capture on c++>=14
// "move" the pointer into a raw pointer
int* myRawPointer = myPointer.release();
// capture the raw pointer as a copy.
std::function<void(void)> = [myRawPointer](){
*myRawPointer = 4;
// ...
delete myRawPointer;
};
// ensure that the pointer's value is not accessible anymore after capturing
myRawPointer = nullptr;
Ja, rohe Hinweise sind in diesen Tagen ziemlich verpönt (und nicht ohne Grund), aber ich denke wirklich, dass sie in diesen seltenen (und vorübergehenden!) Fällen die beste Lösung sind.