在 lambda 中移动捕获

新手上路,请多包涵

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

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

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

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

原文由 user406009 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.9k
2 个回答

C++14 中的广义 lambda 捕获

在 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 ); } );

另请注意,如果您需要将对象从 lambda 移动到其他函数,则需要使 lambda mutable

 go.run( [ u = move(u) ] mutable { do_something_with( std::move(u) ); } );

广义的 lambda 捕获在某种意义上更为通用,捕获的变量可以用如下任何方式初始化:

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

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

更新: Clang 3.4 编译器 于 2014 年 1 月 6 日发布,具有上述功能。

移动捕获的解决方法

这是一个辅助函数的实现 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 中模拟广义 lambda 捕获

这里还有一个想法,关于如何实现广义 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() );
}

这里 lambda 是一个仿函数对象(几乎是一个真正的 lambda),它在传递给 capture() std::move(p) 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) );
}

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

原文由 Ralph Tandetzky 发布,翻译遵循 CC BY-SA 4.0 许可协议

这似乎适用于 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;
}

原文由 andoryu- 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题