Risposte:
In C ++ 14 avremo la cosiddetta cattura lambda generalizzata . Ciò consente l'acquisizione di mosse. Quanto segue sarà il codice legale in C ++ 14:
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 ); } );
Ma è molto più generale nel senso che le variabili catturate possono essere inizializzate con qualcosa del genere:
auto lambda = [value = 0] mutable { return ++value; };
In C ++ 11 questo non è ancora possibile, ma con alcuni trucchi che coinvolgono i tipi di aiuto. Fortunatamente, il compilatore Clang 3.4 implementa già questa fantastica funzionalità. Il compilatore verrà rilasciato a dicembre 2013 o gennaio 2014, se il ritmo di rilascio recente verrà mantenuto.
AGGIORNAMENTO: il compilatore Clang 3.4 è stato rilasciato il 6 gennaio 2014 con la suddetta funzionalità.
Ecco un'implementazione di una funzione di aiuto make_rref
che aiuta con l'acquisizione artificiale delle mosse
#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) };
}
Ed ecco un caso di prova per quella funzione eseguita correttamente sul mio gcc 4.7.3.
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() );
}
Lo svantaggio qui è che lambda
è copiabile e quando viene copiata l'asserzione nel costruttore della copia di rref_impl
fail che porta a un bug di runtime. Di seguito potrebbe essere una soluzione migliore e ancora più generica perché il compilatore rileverà l'errore.
Ecco un'altra idea, su come implementare la cattura lambda generalizzata. L'uso della funzione capture()
(la cui implementazione si trova più in basso) è il seguente:
#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() );
}
Ecco lambda
un oggetto functor (quasi un vero lambda) che è stato catturato std::move(p)
mentre viene passato capture()
. Il secondo argomento di capture
è un lambda che prende la variabile catturata come argomento. Quando lambda
viene utilizzato come oggetto funzione, tutti gli argomenti passati ad esso verranno inoltrati al lambda interno come argomenti dopo la variabile acquisita. (Nel nostro caso non ci sono ulteriori argomenti da inoltrare). In sostanza, accade lo stesso della soluzione precedente. Ecco come capture
viene implementato:
#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) );
}
Questa seconda soluzione è anche più pulita, perché disabilita la copia della lambda, se il tipo acquisito non è copiabile. Nella prima soluzione che può essere verificata solo in fase di esecuzione con un assert()
.
moveCapture
wrapper per passarli come argomenti (questo metodo è usato sopra e in Capn'Proto, una libreria del creatore di protobuff) o accetta semplicemente di aver bisogno di compilatori che lo supportano: P
È inoltre possibile utilizzare std::bind
per acquisire unique_ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
riferimento al valore non può essere associato a un int *
.
myPointer
in questo caso). Pertanto, il codice sopra riportato non viene compilato in VS2013. Si svegliava bene in GCC 4.8 però.
È possibile ottenere la maggior parte di ciò che si desidera utilizzare std::bind
, in questo modo:
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));
Il trucco qui è che invece di catturare il tuo oggetto solo spostamento nell'elenco catture, lo trasformiamo in un argomento e quindi usiamo un'applicazione parziale std::bind
per farlo svanire. Nota che lambda lo prende per riferimento , perché è effettivamente memorizzato nell'oggetto bind. Ho anche aggiunto del codice che scrive sull'oggetto mobile reale, perché è qualcosa che potresti voler fare.
In C ++ 14, puoi usare l'acquisizione lambda generalizzata per ottenere gli stessi fini, con questo codice:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Ma questo codice non ti compra nulla che non avessi in C ++ 11 via std::bind
. (Ci sono alcune situazioni in cui la cattura lambda generalizzata è più potente, ma non in questo caso.)
Ora c'è solo un problema; volevi mettere questa funzione in a std::function
, ma quella classe richiede che la funzione sia CopyConstructible , ma non lo è, è solo MoveConstructible perché memorizza un oggetto std::unique_ptr
che non è CopyConstructible .
È necessario aggirare il problema con la classe wrapper e un altro livello di riferimento indiretto, ma forse non è necessario std::function
affatto. A seconda delle esigenze, potresti essere in grado di utilizzare std::packaged_task
; farebbe lo stesso lavoro di std::function
, ma non richiede che la funzione sia copiabile, solo mobile (allo stesso modo, std::packaged_task
è solo mobile). Il rovescio della medaglia è che, poiché è destinato ad essere utilizzato in combinazione con std :: future, è possibile chiamarlo solo una volta.
Ecco un breve programma che mostra tutti questi concetti.
#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
}
Ho messo il programma sopra su Coliru , quindi puoi correre e giocare con il codice.
Ecco alcuni output tipici ...
- &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
Puoi vedere le posizioni di heap riutilizzate, dimostrando che std::unique_ptr
funziona correttamente. Vedi anche la funzione stessa spostarsi quando la mettiamo in un wrapper a cui ci nutriamo std::function
.
Se passiamo all'utilizzo std::packaged_task
, diventa l'ultima parte
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
quindi vediamo che la funzione è stata spostata, ma invece di spostarsi nell'heap, è all'interno di quello std::packaged_task
che è nello stack.
Spero che questo ti aiuti!
In ritardo, ma poiché alcune persone (incluso me) sono ancora bloccate su c ++ 11:
Ad essere sincero, non mi piace molto nessuna delle soluzioni pubblicate. Sono sicuro che funzioneranno, ma richiedono un sacco di roba aggiuntiva e / o std::bind
sintassi criptica ... e non credo che valga la pena fare una soluzione così temporanea che verrà comunque rifattorizzata durante l'aggiornamento a c ++> = 14. Quindi penso che la soluzione migliore sia quella di evitare di spostare completamente l'acquisizione per c ++ 11.
Di solito è la soluzione più semplice e leggibile da usare std::shared_ptr
, che è copiabile e quindi la mossa è completamente evitabile. L'unico inconveniente è che è un po 'meno efficiente, ma in molti casi l'efficienza non è così importante.
// 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.
.
Se si verifica il caso molto raro, che è davvero obbligatorio per move
il puntatore (ad esempio, si desidera eliminare esplicitamente un puntatore in un thread separato a causa della lunga durata dell'eliminazione, o le prestazioni sono assolutamente cruciali), è praticamente l'unico caso in cui continuo a utilizzare puntatori non elaborati in c ++ 11. Questi sono ovviamente anche copiabili.
Di solito segnare questi rari casi con a //FIXME:
per assicurarmi che venga refactored dopo l'aggiornamento a c ++ 14.
// 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](){
std::unique_ptr<int> capturedPointer(myRawPointer);
*capturedPointer = 4;
};
// ensure that the pointer's value is not accessible anymore after capturing
myRawPointer = nullptr;
Sì, in questi giorni i puntatori grezzi sono piuttosto disapprovati (e non senza motivo), ma penso davvero che in questi rari (e temporanei!) Casi siano la soluzione migliore.
Stavo guardando queste risposte, ma ho trovato il legame difficile da leggere e capire. Quindi quello che ho fatto è stato creare una classe che si spostava invece su copia. In questo modo, è esplicito con ciò che sta facendo.
#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";
}
La move_with_copy_ctor
classe e la sua funzione di supporto funzioneranno make_movable()
con qualsiasi oggetto mobile ma non copiabile. Per accedere all'oggetto avvolto, utilizzare il operator()()
.
Uscita prevista:
valore: 1 l'oggetto non è stato ancora cancellato valore: 1 Distruggere 000000DFDD172280 l'oggetto è stato cancellato
Bene, l'indirizzo del puntatore può variare. ;)