首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在lambda中移动捕获

在lambda中移动捕获
EN

Stack Overflow用户
提问于 2011-12-27 09:28:14
回答 5查看 91.7K关注 0票数 183

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

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

代码语言:javascript
运行
复制
std::unique_ptr<int> myPointer(new int);

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

回答 5

Stack Overflow用户

回答已采纳

发布于 2013-12-19 05:57:43

C++14中的广义lambda捕获

在C++14中,我们将有所谓的generalized lambda capture。这将启用移动捕获。以下是C++14中的合法代码:

代码语言:javascript
运行
复制
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 ); } ); 

但它更具一般性,因为捕获的变量可以用如下所示的方式初始化:

代码语言:javascript
运行
复制
auto lambda = [value = 0] mutable { return ++value; };

在C++11中,这是不可能的,但有一些技巧涉及到帮助器类型。幸运的是,Clang 3.4编译器已经实现了这个令人敬畏的特性。如果保留recent release pace,编译器将在2013年12月或2014年1月发布。

更新: Clang 3.4 compiler已于2014年1月6日发布,并具有上述功能。

移动捕获的解决方法

下面是一个辅助函数make_rref的实现,它可以帮助进行人工动作捕捉

代码语言:javascript
运行
复制
#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上成功运行的函数的测试用例。

代码语言:javascript
运行
复制
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中模拟通用的λ捕获

这里还有一个想法,关于如何实现通用的lambda捕获。函数capture() (其实现可以在下面找到)的用法如下:

代码语言:javascript
运行
复制
#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是一个函数器对象(几乎是一个真正的lambda),它在将std::move(p)传递给capture()时捕获了它。capture的第二个参数是一个λ,它将捕获的变量作为参数。当lambda用作函数对象时,传递给它的所有参数都将作为捕获变量后的参数转发给内部lambda。(在我们的例子中,没有更多的参数要转发)。本质上,与前一个解决方案中的情况相同。下面是capture的实现方式:

代码语言:javascript
运行
复制
#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) );
}

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

票数 195
EN

Stack Overflow用户

发布于 2012-10-05 18:48:32

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

代码语言:javascript
运行
复制
std::function<void()> f = std::bind(
                              [] (std::unique_ptr<int>& p) { *p=4; },
                              std::move(myPointer)
                          );
票数 80
EN

Stack Overflow用户

发布于 2014-05-02 15:22:24

您可以使用std::bind实现您想要的大部分功能,如下所示:

代码语言:javascript
运行
复制
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使用部分应用程序使其消失。请注意,lambda通过引用获取它,因为它实际上存储在bind对象中。我还添加了写入实际可移动对象的代码,因为这可能是您想要做的事情。

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

代码语言:javascript
运行
复制
std::unique_ptr<int> myPointer(new int{42});

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

但是这段代码不会通过std::bind为您带来任何在C++11中没有的东西。(在某些情况下,通用lambda捕获功能更强大,但在这种情况下不是这样。)

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

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

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

代码语言:javascript
运行
复制
#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
}

我放了一个上面的程序on Coliru,这样你就可以运行和使用代码了。

下面是一些典型的输出。

代码语言:javascript
运行
复制
- &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,那么最后一部分就变成了

代码语言:javascript
运行
复制
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内部。

希望这能有所帮助!

票数 25
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8640393

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档