C++ 在 lambda 中移动捕获

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/8640393/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 18:55:10  来源:igfitidea点击:

Move capture in lambda

c++lambdac++11rvalue-reference

提问by Lalaland

How do I capture by move (also known as rvalue reference) in a C++11 lambda?

如何在 C++11 lambda 中通过移动(也称为右值引用)进行捕获?

I am trying to write something like this:

我正在尝试写这样的东西:

std::unique_ptr<int> myPointer(new int);

std::function<void(void)> example = [std::move(myPointer)]{
   *myPointer = 4;
};

回答by Ralph Tandetzky

Generalized lambda capture in C++14

C++14 中的广义 lambda 捕获

In C++14 we will have the so called generalized lambda capture. This enables move capture. The following will be legal code in C++14:

在 C++14 中,我们将有所谓的广义 lambda 捕获。这将启用移动捕获。以下将是 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 ); } ); 

But it is much more general in the sense that captured variables can be initialized with anything like so:

但从某种意义上说,捕获的变量可以用以下任何东西初始化:

auto lambda = [value = 0] mutable { return ++value; };

In C++11 this is not possible yet, but with some tricks that involve helper types. Fortunately, the Clang 3.4 compiler already implements this awesome feature. The compiler will be released December 2013 or January 2014, if the recent release pacewill be kept.

在 C++11 中,这是不可能的,但有一些涉及辅助类型的技巧。幸运的是,Clang 3.4 编译器已经实现了这个很棒的功能。如果保持最近的发布速度,编译器将在 2013 年 12 月或 2014 年 1 月发布

UPDATE:The Clang 3.4 compilerwas released on 6 Jan 2014 with the said feature.

更新:锵3.4编译器发布于2014年1月6日与所述特征。

A workaround for move capture

移动捕获的解决方法

Here's an implementation of a helper function make_rrefwhich helps with artificial move capture

这是一个帮助函数的实现,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) };
}

And here's a test case for that function that ran successfully on my gcc 4.7.3.

这是该函数在我的 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() );
}

The drawback here is that lambdais copyable and when copied the assertion in the copy constructor of rref_implfails leading to a runtime bug. The following might be a better and even more generic solution because the compiler will catch the error.

这里的缺点是它lambda是可复制的,并且在复制rref_impl失败的复制构造函数中的断言会导致运行时错误。以下可能是更好甚至更通用的解决方案,因为编译器会捕获错误。

Emulating generalized lambda capture in C++11

在 C++11 中模拟广义 lambda 捕获

Here's one more idea, on how to implement generalized lambda capture. The use of the function capture()(whose implementation is found further down) is as follows:

这是关于如何实现广义 lambda 捕获的另一个想法。该函数的使用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() );
}

Here lambdais a functor object (almost a real lambda) which has captured std::move(p)as it is passed to capture(). The second argument of captureis a lambda which takes the captured variable as an argument. When lambdais used as a function object, then all arguments that are passed to it will be forwarded to the internal lambda as arguments after the captured variable. (In our case there are no further arguments to be forwarded). Essentially, the same as in the previous solution happens. Here's how captureis implemented:

lambda是一个函子对象(几乎是一个真正的 lambda),std::move(p)它在传递给capture(). 的第二个参数capture是一个 lambda,它将捕获的变量作为参数。当lambda用作函数对象时,则传递给它的所有参数都将在捕获的变量之后作为参数转发到内部 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) );
}

This second solution is also cleaner, because it disables copying the lambda, if the captured type is not copyable. In the first solution that can only be checked at runtime with an assert().

第二个解决方案也更简洁,因为如果捕获的类型不可复制,它会禁用 lambda 的复制。在第一个解决方案中,只能在运行时使用assert().

回答by marton78

You could also use std::bindto capture the unique_ptr:

您还可以std::bind用来捕获unique_ptr

std::function<void()> f = std::bind(
                              [] (std::unique_ptr<int>& p) { *p=4; },
                              std::move(myPointer)
                          );

回答by Charphacy

You can achieve most of what you want using std::bind, like this:

您可以使用 实现您想要的大部分内容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));

The trick here is that instead of capturing your move-only object in the captures list, we make it an argument and then use partial application via std::bindto make it vanish. Note that the lambda takes it by reference, because it's actually stored in the bind object. I also added code that writesto the actual movable object, because that's something you might want to do.

这里的技巧是,我们不是在捕获列表中捕获仅移动对象,而是将其作为参数,然后使用部分应用程序通过std::bind使其消失。请注意,lambda通过引用获取它,因为它实际上存储在绑定对象中。我还添加了写入实际可移动对象的代码,因为这是您可能想要做的事情。

In C++14, you can use generalized lambda capture to achieve the same ends, with this code:

在 C++14 中,您可以使用广义 lambda 捕获来实现相同的目的,代码如下:

std::unique_ptr<int> myPointer(new int{42});

auto lambda = [myPointerCapture = std::move(myPointer)]() mutable {
    *myPointerCapture = 56;
    myPointerCapture.reset(new int{237});
};

But this code doesn't buy you anything you didn't have in C++11 via std::bind. (There are some situations where generalized lambda capture is more powerful, but not in this case.)

但是这段代码不会通过std::bind. (在某些情况下,广义 lambda 捕获更强大,但在这种情况下并非如此。)

Now there is just one problem; you wanted to put this function in a std::function, but that class requires that the function be CopyConstructible, but it isn't, it's only MoveConstructiblebecause it's storing a std::unique_ptrwhich isn't CopyConstructible.

现在只有一个问题;你想把这个函数放在 a 中std::function,但那个类要求函数是CopyConstructible,但它不是,它只是MoveConstructible因为它存储的 astd::unique_ptr不是CopyConstructible

You to work around the issue with wrapper class and another level of indirection, but perhaps you don't need std::functionat all. Depending on your needs, you may be able to use std::packaged_task; it'd do the same job as std::function, but it doesn't require the function to be copyable, only movable (similarly, std::packaged_taskis only movable). The downside is that because it's intended to be used in conjunction with std::future, you can only call it once.

您可以使用包装类和另一个间接级别来解决这个问题,但也许您根本不需要std::function。根据您的需要,您可以使用std::packaged_task; 它会做与 相同的工作std::function,但它不需要函数是可复制的,只需要可移动(类似地,std::packaged_task只需要可移动)。缺点是因为它旨在与 std::future 结合使用,所以您只能调用它一次。

Here's a short program that shows all of these concepts.

这是一个简短的程序,展示了所有这些概念。

#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
}

I've put a the above program on Coliru, so you can run and play with the code.

我已经把上面的程序放在 Coliru 上,所以你可以运行和玩代码。

Here's some typical output...

这是一些典型的输出...

- &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

You get to see heap locations being reused, showing that the std::unique_ptris working properly. You also see the function itself move around when we stash it in a wrapper we feed to std::function.

你会看到堆位置被重用,表明它std::unique_ptr工作正常。当我们将它存放在我们提供给 的包装器中时,您还会看到函数本身在移动std::function

If we switch to using std::packaged_task, it the last part becomes

如果我们切换到 using 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

so we see that the function has been moved, but rather than getting moved onto the heap, it's inside the std::packaged_taskthat's on the stack.

所以我们看到函数已经被移动了,但它并没有被移动到堆上,而是std::packaged_task在栈上。

Hope this helps!

希望这可以帮助!

回答by user2328447

Late, but as some people (including me) are still stuck on c++11:

晚了,但因为有些人(包括我)仍然停留在 c++11 上:

To be honest, I don't really like any of the posted solutions. I'm sure they will work, but they require a lot of additional stuff and/or cryptical std::bindsyntax... and I don't think that it's worth the effort for such a temporary solution which will be refactored anyway when upgrading to c++ >= 14. So I think the best solution is to avoid move capturing for c++11 completely.

老实说,我真的不喜欢任何已发布的解决方案。我确信它们会起作用,但它们需要很多额外的东西和/或神秘的std::bind语法......而且我认为这样一个临时解决方案不值得付出努力,在升级到 C++ 时无论如何都会重构> = 14. 所以我认为最好的解决方案是完全避免 c++11 的移动捕获。

Usually the simplest and best readable solution is to use std::shared_ptr, which are copyable and so the move is completely avoidable. Downside is, that it's a little less efficient, but in many cases efficiency is not so important.

通常最简单和最好的可读解决方案是使用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.

.

.

If the very rare case occurs, that it's really mandatory to movethe pointer (e.g. you want to explicitly delete a pointer in a separate thread due to lengthy delete duration, or performance is absolutely crucial), that's pretty much the only case where I still use raw pointers in c++11. These are of course also copyable.

如果发生非常罕见的情况,它确实move是指针所必需的(例如,由于删除持续时间过长,您想在单独的线程中显式删除指针,或者性能绝对至关重要),这几乎是我仍然使用的唯一情况C++11 中的原始指针。这些当然也是可复制的。

Usually I mark these rare cases with a //FIXME:to ensure that it's refactored once upgrading to c++ 14.

通常我用 a 标记这些罕见的情况,//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;

Yes, raw pointers are pretty frowned upon these days (and not without reason), but I really think in these rare (and temporary!) cases they are the best solution.

是的,这些天(并非没有理由)原始指针非常不受欢迎,但我真的认为在这些罕见(和临时!)情况下,它们是最好的解决方案。

回答by andoryu-

This seems to work on gcc4.8

这似乎适用于 gcc4.8

#include <memory>
#include <iostream>

struct Foo {};

void bar(std::unique_ptr<Foo> p) {
    std::cout << "bar\n";
}

int main() {
    std::unique_ptr<Foo> p(new Foo);
    auto f = [ptr = std::move(p)]() mutable {
        bar(std::move(ptr));
    };
    f();
    return 0;
}

回答by Adrian

I was looking at these answers, but I found bind hard to read and understand. So what I did was make a class that moved on copy instead. In this way, it is explicit with what it is doing.

我正在查看这些答案,但我发现 bind 难以阅读和理解。所以我所做的是创建一个以复制方式移动的类。这样,它在做什么就很明确了。

#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";
}

The move_with_copy_ctorclass and it's helper function make_movable()will work with any movable but not copyable object. To get access to the wrapped object, use the operator()().

move_with_copy_ctor类和它的辅助函数make_movable()将与任何移动,但不可拷贝对象工作。要访问包装的对象,请使用operator()().

Expected output:

预期输出:

value: 1
object still not deleted
value: 1
Destroying 000000DFDD172280
object was deleted

Well, the pointer address may vary. ;)

好吧,指针地址可能会有所不同。;)

Demo

Demo