Вы можете достичь большей части того, что вы хотите, используя 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
, чтобы оно исчезло.Обратите внимание, что лямбда принимает значение по ссылке , потому что оно на самом деле хранится в объекте связывания.Я также добавил код, который записывает в фактический подвижный объект, потому что это то, что вы, возможно, захотите сделать.
В 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
, который находится в стеке.
Надеюсь, это поможет!