Uogólnione przechwytywanie lambda w C ++ 14
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ą.
Obejście problemu przechwytywania ruchu
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.
Emulowanie uogólnionego przechwytywania lambda w C ++ 11
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ą: PMożesz również użyć
std::bind
do przechwyceniaunique_ptr
:źródło
unique_ptr
odwołanie do wartości r nie może być powiązane z plikiemint *
.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: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:
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ć zstd::packaged_task
; wykonałby to samo zadaniestd::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.
Umieściłem powyższy program na Coliru , więc możesz uruchomić i bawić się kodem.
Oto kilka typowych wyników ...
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 dostarczamystd::function
.Jeśli przejdziemy na używanie
std::packaged_task
, stanie się ostatnia część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!
źródło
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..
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.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.
źródło
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.
move_with_copy_ctor
Klasy i jest to funkcja pomocnikamake_movable()
będzie współpracować z każdym, ale nie copyable ruchomego obiektu. Aby uzyskać dostęp do opakowanego obiektu, użyj rozszerzeniaoperator()()
.Oczekiwany wynik:
Cóż, adres wskaźnika może się różnić. ;)
Demo
źródło
Wygląda na to, że działa na gcc4.8
źródło