Ответы:
В 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
не может привести к ошибке во время выполнения. Следующее может быть лучшим и даже более общим решением, потому что компилятор поймает ошибку.
Вот еще одна идея, как реализовать обобщенный лямбда-захват. Использование функции 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
:
std::function<void()> f = std::bind(
[] (std::unique_ptr<int>& p) { *p=4; },
std::move(myPointer)
);
unique_ptr
ссылка на rvalue не может привязываться к int *
.
myPointer
в данном случае). Поэтому приведенный выше код не компилируется в VS2013. В GCC 4.8 он прекрасно работает.
Вы можете достичь большей части того, что вы хотите использовать std::bind
, как это:
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));
Хитрость в том, что вместо того, чтобы захватывать ваш объект только для перемещения в списке захватов, мы делаем его аргументом, а затем используем частичное применение через, std::bind
чтобы сделать его исчезающим. Обратите внимание, что лямбда использует его по ссылке , потому что он на самом деле хранится в объекте bind. Я также добавил код, который записывает в реально перемещаемый объект, потому что это то, что вы, возможно, захотите сделать.
В C ++ 14 вы можете использовать обобщенный лямбда-захват для достижения тех же целей с помощью этого кода:
std::unique_ptr<int> myPointer(new int{42});
auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
*myPointerCapture = 56;
myPointerCapture.reset(new int{237});
};
Но этот код не покупает вам ничего, чего у вас не было в C ++ 11 через std::bind
. (В некоторых случаях обобщенный лямбда-захват более мощный, но не в этом случае.)
Теперь есть только одна проблема; Вы хотели поместить эту функцию в std::function
, но этот класс требует, чтобы функция была CopyConstructible , но это не так, это только MoveConstructible, потому что она хранит объект, std::unique_ptr
который не является CopyConstructible. .
Вам нужно обойти проблему с классом-оберткой и другим уровнем косвенности, но, возможно, вам это вообще не нужно std::function
. В зависимости от ваших потребностей, вы можете использовать std::packaged_task
; он будет выполнять ту же работу, что и std::function
, но он не требует, чтобы функция была копируемой, только подвижной (аналогично, std::packaged_task
только подвижной). Недостатком является то, что, поскольку он предназначен для использования вместе с std :: future, вы можете вызвать его только один раз.
Вот короткая программа, которая показывает все эти понятия.
#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
}
Я поставил вышеупомянутую программу на Coliru , чтобы вы могли запускать и играть с кодом.
Вот типичный вывод ...
- &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
Вы увидите, что места кучи используются повторно, показывая, что они std::unique_ptr
работают правильно. Вы также видите, как сама функция перемещается, когда мы храним ее в оболочке, которой мы передаем std::function
.
Если мы переключимся на использование std::packaged_task
, то последняя часть становится
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
Итак, мы видим, что функция была перемещена, но вместо того, чтобы перемещаться в кучу, она находится внутри std::packaged_task
стека.
Надеюсь это поможет!
Поздно, но как некоторые люди (включая меня) все еще застряли на C ++ 11:
Если честно, мне не очень нравится ни одно из опубликованных решений. Я уверен, что они будут работать, но они требуют много дополнительных вещей и / или зашифрованного std::bind
синтаксиса ... и я не думаю, что это стоит усилий для такого временного решения, которое в любом случае будет изменено при обновлении до c ++> = 14. Поэтому я думаю, что лучшее решение - полностью избежать захвата хода для c ++ 11.
Обычно самое простое и удобочитаемое решение - это использование std::shared_ptr
, которое можно копировать, и поэтому этот шаг можно избежать. Недостатком является то, что он немного менее эффективен, но во многих случаях эффективность не так важна.
// 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.
,
Если происходит очень редкий случай, то это действительно обязательно move
указателя (например, вы хотите явно удалить указатель в отдельном потоке из-за длительности удаления или производительности крайне важно), это практически единственный случай, когда я все еще использую сырые указатели в с ++ 11. Это, конечно, также копируемые.
Обычно я отмечаю эти редкие случаи, //FIXME:
чтобы убедиться, что они обновлены после обновления до 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;
Да, необработанные указатели в наши дни довольно недовольны (и не без причины), но я действительно думаю, что в этих редких (и временных!) Случаях они являются лучшим решением.
Я смотрел на эти ответы, но мне было трудно читать и понимать. Итак, я сделал класс, который вместо этого перешел на копию. Таким образом, это ясно с тем, что он делает.
#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
Класс , и это вспомогательная функция make_movable()
будет работать с любым подвижным , но не Copyable объекта. Чтобы получить доступ к обернутому объекту, используйте operator()()
.
Ожидаемый результат:
значение: 1 объект еще не удален значение: 1 Уничтожение 000000DFDD172280 объект был удален
Ну, адрес указателя может отличаться. ;)