Odpowiedzi:
W C ++ 14 będziemy mieli tzw. Uogólnione przechwytywanie lambda . Umożliwia to przechwytywanie ruchu. Poniższy kod będzie prawnym kodem w 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 ); } );
Ale jest o wiele bardziej ogólny w tym sensie, że przechwycone zmienne można zainicjować za pomocą czegoś takiego:
auto lambda = [value = 0] mutable { return ++value; };
W C ++ 11 nie jest to jeszcze możliwe, ale z pewnymi sztuczkami, które obejmują typy pomocnicze. Na szczęście kompilator Clang 3.4 już implementuje tę niesamowitą funkcję. Kompilator zostanie wydany w grudniu 2013 lub styczniu 2014, jeśli utrzymane zostanie ostatnie tempo wydawania .
UPDATE: Clang 3.4 kompilator został wydany w dniu 6 stycznia 2014 roku ze wspomnianą funkcją.
Oto implementacja funkcji pomocniczej, make_rref
która pomaga w przechwytywaniu sztucznego ruchu
#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) };
}
A oto przypadek testowy dla tej funkcji, która została pomyślnie uruchomiona na moim 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() );
}
Wadą jest to, że lambda
można je skopiować, a po skopiowaniu twierdzenie w konstruktorze kopiującym rref_impl
kończy się niepowodzeniem prowadzącym do błędu w czasie wykonywania. Poniższe może być lepszym i jeszcze bardziej ogólnym rozwiązaniem, ponieważ kompilator wykryje błąd.
Oto jeszcze jeden pomysł, jak zaimplementować uogólnione przechwytywanie lambda. Użycie funkcji capture()
(której implementację opisano poniżej) jest następujące:
#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() );
}
Oto lambda
obiekt funktora (prawie prawdziwa lambda), który został przechwycony std::move(p)
podczas przekazywania capture()
. Drugim argumentem capture
jest lambda, która przyjmuje przechwyconą zmienną jako argument. Gdy lambda
jest używany jako obiekt funkcji, wszystkie argumenty, które są do niego przekazywane, będą przekazywane do wewnętrznej lambdy jako argumenty po przechwyconej zmiennej. (W naszym przypadku nie ma dalszych argumentów do przekazania). Zasadniczo dzieje się to samo, co w poprzednim rozwiązaniu. Oto jak capture
jest wdrażane:
#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) );
}
To drugie rozwiązanie jest również czystsze, ponieważ wyłącza kopiowanie lambdy, jeśli przechwycony typ nie jest kopiowalny. W pierwszym rozwiązaniu, które można sprawdzić tylko w czasie wykonywania za pomocą pliku assert()
.
moveCapture
opakowania, aby przekazać je jako argumenty (ta metoda jest używana powyżej iw Capn'Proto, bibliotece twórcy protobuffów) lub po prostu zaakceptuj, że potrzebujesz kompilatorów, które ją obsługują: P
Możesz również użyć std::bind
do przechwycenia unique_ptr
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
odwołanie do wartości r nie może być powiązane z plikiem int *
.
myPointer
w tym przypadku). Dlatego powyższy kod nie kompiluje się w VS2013. Jednak w GCC 4.8 działa dobrze.
Możesz osiągnąć większość tego, co chcesz, używając std::bind
, na przykład:
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));
Sztuczka polega na tym, że zamiast przechwytywać obiekt tylko do przenoszenia na liście przechwytywania, ustawiamy go jako argument, a następnie używamy częściowej aplikacji via, std::bind
aby zniknął. Zauważ, że lambda przyjmuje ją przez odniesienie , ponieważ jest faktycznie przechowywana w obiekcie bind. Dodałem również kod, który pisze do rzeczywistego ruchomego obiektu, ponieważ jest to coś, co możesz chcieć zrobić.
W C ++ 14 możesz użyć uogólnionego przechwytywania lambda, aby osiągnąć te same cele, za pomocą tego kodu:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Ale ten kod nie kupi ci niczego, czego nie miałeś w C ++ 11 za pośrednictwem std::bind
. (Są sytuacje, w których uogólnione przechwytywanie lambda jest bardziej wydajne, ale nie w tym przypadku).
Teraz jest tylko jeden problem; chciałeś umieścić tę funkcję w a std::function
, ale ta klasa wymaga, aby funkcja była CopyConstructible , ale tak nie jest, to tylko MoveConstructible, ponieważ przechowuje element, std::unique_ptr
który nie jest CopyConstructible .
Możesz obejść ten problem z klasą opakowującą i innym poziomem pośrednictwa, ale być może wcale nie potrzebujesz std::function
. W zależności od potrzeb możesz skorzystać z std::packaged_task
; wykonałby to samo zadanie std::function
, ale nie wymaga, aby funkcja była kopiowalna, tylko przenośna (podobnie, std::packaged_task
jest tylko przenośna). Wadą jest to, że ponieważ jest przeznaczony do użycia w połączeniu ze std :: future, możesz go wywołać tylko raz.
Oto krótki program, który pokazuje wszystkie te koncepcje.
#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
}
Umieściłem powyższy program na Coliru , więc możesz uruchomić i bawić się kodem.
Oto kilka typowych wyników ...
- &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
Możesz zobaczyć ponownie używane lokalizacje sterty, pokazując, że std::unique_ptr
działa poprawnie. Widzisz również, że sama funkcja porusza się, gdy przechowujemy ją w opakowaniu, do którego dostarczamy std::function
.
Jeśli przejdziemy na używanie std::packaged_task
, stanie się ostatnia część
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
więc widzimy, że funkcja została przeniesiona, ale zamiast zostać przeniesiona na stertę, znajduje się wewnątrz std::packaged_task
stosu.
Mam nadzieję że to pomoże!
Późno, ale ponieważ niektórzy ludzie (w tym ja) wciąż tkwią w c ++ 11:
Szczerze mówiąc, nie podoba mi się żadne z opublikowanych rozwiązań. Jestem pewien, że zadziałają, ale wymagają mnóstwa dodatkowych rzeczy i / lub kryptograficznej std::bind
składni ... i nie sądzę, że warto się wysilić na takie tymczasowe rozwiązanie, które i tak zostanie refaktoryzowane przy aktualizacji do c ++> = 14. Myślę więc, że najlepszym rozwiązaniem jest całkowite uniknięcie przechwytywania przenoszenia dla c ++ 11.
Zwykle najprostszym i najlepiej czytelnym rozwiązaniem jest użycie std::shared_ptr
, które można skopiować, dzięki czemu można całkowicie uniknąć przeniesienia. Wadą jest to, że jest trochę mniej wydajna, ale w wielu przypadkach wydajność nie jest tak ważna.
// 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.
.
Jeśli zdarzy się bardzo rzadki przypadek, jest to naprawdę obowiązkowe move
wskaźnika (np. Chcesz jawnie usunąć wskaźnik w osobnym wątku ze względu na długi czas usuwania lub wydajność jest absolutnie kluczowa), to jest prawie jedyny przypadek, w którym nadal używam surowe wskaźniki w C ++ 11. Można je oczywiście skopiować.
Zwykle oznaczam te rzadkie przypadki znakiem, //FIXME:
aby upewnić się, że jest on refaktoryzowany po uaktualnieniu do 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;
Tak, surowe wskazówki są obecnie dość źle widziane (i nie bez powodu), ale naprawdę myślę, że w tych rzadkich (i tymczasowych!) Przypadkach są one najlepszym rozwiązaniem.
Patrzyłem na te odpowiedzi, ale okazało się, że bind jest trudny do odczytania i zrozumienia. Więc zrobiłem zajęcia, które zamiast tego przeniosły się na kopię. W ten sposób jasno określa to, co robi.
#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";
}
move_with_copy_ctor
Klasy i jest to funkcja pomocnika make_movable()
będzie współpracować z każdym, ale nie copyable ruchomego obiektu. Aby uzyskać dostęp do opakowanego obiektu, użyj rozszerzenia operator()()
.
Oczekiwany wynik:
wartość: 1 obiekt nadal nie został usunięty wartość: 1 Niszczenie 000000DFDD172280 obiekt został usunięty
Cóż, adres wskaźnika może się różnić. ;)