Обобщенный лямбда-захват в C ++ 14
В C ++ 14 у нас будет так называемый обобщенный лямбда-захват . Это позволяет захватить движение. Следующее будет юридическим кодом в 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 ); } );
Но это гораздо более общий смысл в том смысле, что захваченные переменные могут быть инициализированы с помощью чего-либо подобного:
auto lambda = [value = 0] mutable { return ++value; };
В C ++ 11 это пока невозможно, но с некоторыми приемами, которые включают вспомогательные типы. К счастью, компилятор Clang 3.4 уже реализует эту замечательную функцию. Компилятор будет выпущен в декабре 2013 года или январе 2014 года, если будет сохранен темп последних выпусков .
UPDATE: Clang 3.4 компилятор был выпущен 6 января 2014 года с указанной функцией.
Обходной путь для захвата хода
Вот реализация вспомогательной функции, make_rref
которая помогает с искусственным захватом движения
#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) };
}
И вот тестовый пример для той функции, которая успешно работала на моем 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() );
}
Недостатком здесь является то, что lambda
копируемое и при копировании утверждение в конструкторе копирования rref_impl
не может привести к ошибке во время выполнения. Следующее может быть лучшим и даже более общим решением, потому что компилятор поймает ошибку.
Эмуляция обобщенного лямбда-захвата в C ++ 11
Вот еще одна идея, как реализовать обобщенный лямбда-захват. Использование функции capture()
(реализация которой находится ниже) выглядит следующим образом:
#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() );
}
Вот lambda
объект функтора (почти настоящая лямбда), который захватывается std::move(p)
при его передаче capture()
. Второй аргумент capture
- это лямбда, которая принимает захваченную переменную в качестве аргумента. Когда lambda
используется в качестве объекта функции, все передаваемые ему аргументы будут пересылаться во внутреннюю лямбду в качестве аргументов после захваченной переменной. (В нашем случае нет дальнейших аргументов для пересылки). По сути, происходит то же, что и в предыдущем решении. Вот как capture
это реализовано:
#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) );
}
Это второе решение также является более чистым, потому что оно запрещает копирование лямбды, если захваченный тип не подлежит копированию. В первом решении это можно проверить только во время выполнения с помощью assert()
.
moveCapture
оболочку для передачи их в качестве аргументов (этот метод используется выше и в Capn'Proto, библиотеке создателя протобаффов), либо заставьте просто принять, что вам требуются компиляторы, которые его поддерживают: PВы также можете использовать
std::bind
для захватаunique_ptr
:источник
unique_ptr
ссылка на rvalue не может привязываться кint *
.myPointer
в данном случае). Поэтому приведенный выше код не компилируется в VS2013. В GCC 4.8 он прекрасно работает.Вы можете достичь большей части того, что вы хотите использовать
std::bind
, как это:Хитрость в том, что вместо того, чтобы захватывать ваш объект только для перемещения в списке захватов, мы делаем его аргументом, а затем используем частичное применение через,
std::bind
чтобы сделать его исчезающим. Обратите внимание, что лямбда использует его по ссылке , потому что он на самом деле хранится в объекте bind. Я также добавил код, который записывает в реально перемещаемый объект, потому что это то, что вы, возможно, захотите сделать.В C ++ 14 вы можете использовать обобщенный лямбда-захват для достижения тех же целей с помощью этого кода:
Но этот код не покупает вам ничего, чего у вас не было в C ++ 11 через
std::bind
. (В некоторых случаях обобщенный лямбда-захват более мощный, но не в этом случае.)Теперь есть только одна проблема; Вы хотели поместить эту функцию в
std::function
, но этот класс требует, чтобы функция была CopyConstructible , но это не так, это только MoveConstructible, потому что она хранит объект,std::unique_ptr
который не является CopyConstructible. .Вам нужно обойти проблему с классом-оберткой и другим уровнем косвенности, но, возможно, вам это вообще не нужно
std::function
. В зависимости от ваших потребностей, вы можете использоватьstd::packaged_task
; он будет выполнять ту же работу, что иstd::function
, но он не требует, чтобы функция была копируемой, только подвижной (аналогично,std::packaged_task
только подвижной). Недостатком является то, что, поскольку он предназначен для использования вместе с std :: future, вы можете вызвать его только один раз.Вот короткая программа, которая показывает все эти понятия.
Я поставил вышеупомянутую программу на Coliru , чтобы вы могли запускать и играть с кодом.
Вот типичный вывод ...
Вы увидите, что места кучи используются повторно, показывая, что они
std::unique_ptr
работают правильно. Вы также видите, как сама функция перемещается, когда мы храним ее в оболочке, которой мы передаемstd::function
.Если мы переключимся на использование
std::packaged_task
, то последняя часть становитсяИтак, мы видим, что функция была перемещена, но вместо того, чтобы перемещаться в кучу, она находится внутри
std::packaged_task
стека.Надеюсь это поможет!
источник
Поздно, но как некоторые люди (включая меня) все еще застряли на C ++ 11:
Если честно, мне не очень нравится ни одно из опубликованных решений. Я уверен, что они будут работать, но они требуют много дополнительных вещей и / или зашифрованного
std::bind
синтаксиса ... и я не думаю, что это стоит усилий для такого временного решения, которое в любом случае будет изменено при обновлении до c ++> = 14. Поэтому я думаю, что лучшее решение - полностью избежать захвата хода для c ++ 11.Обычно самое простое и удобочитаемое решение - это использование
std::shared_ptr
, которое можно копировать, и поэтому этот шаг можно избежать. Недостатком является то, что он немного менее эффективен, но во многих случаях эффективность не так важна.,
Если происходит очень редкий случай, то это действительно обязательно
move
указателя (например, вы хотите явно удалить указатель в отдельном потоке из-за длительности удаления или производительности крайне важно), это практически единственный случай, когда я все еще использую сырые указатели в с ++ 11. Это, конечно, также копируемые.Обычно я отмечаю эти редкие случаи,
//FIXME:
чтобы убедиться, что они обновлены после обновления до c ++ 14.Да, необработанные указатели в наши дни довольно недовольны (и не без причины), но я действительно думаю, что в этих редких (и временных!) Случаях они являются лучшим решением.
источник
Я смотрел на эти ответы, но мне было трудно читать и понимать. Итак, я сделал класс, который вместо этого перешел на копию. Таким образом, это ясно с тем, что он делает.
move_with_copy_ctor
Класс , и это вспомогательная функцияmake_movable()
будет работать с любым подвижным , но не Copyable объекта. Чтобы получить доступ к обернутому объекту, используйтеoperator()()
.Ожидаемый результат:
Ну, адрес указателя может отличаться. ;)
Demo
источник
Кажется, это работает на gcc4.8
источник