如何从 C++14 中的广义 lambda 捕获返回包含 std::unique_ptr 的 std::function?

Posted

技术标签:

【中文标题】如何从 C++14 中的广义 lambda 捕获返回包含 std::unique_ptr 的 std::function?【英文标题】:How to return a std::function that contains a std::unique_ptr from a generalized lambda capture in C++14? 【发布时间】:2015-10-19 21:28:04 【问题描述】:

我们如何从 C++14 中的通用 lambda 捕获返回包含 std::unique_ptrstd::function?具体来说,在下面的代码中

// For std::function
#include <functional>

// For std::iostream
#include <iostream>

// For std::unique_ptr
#include <memory>

#if 0
std::function <void()> make_foo() 
    auto x = std::make_unique <int> (3);
    return [x=std::move(x)]() 
        std::cout << *x << std::endl;
    ;

#endif

int main() 
    auto x = std::make_unique <int> (3);
    auto foo = [x=std::move(x)]() 
        std::cout << *x << std::endl;
    ;
    foo();

在 GCC 4.9.2 和 C++14 开启的情况下运行一切正常。具体来说,它表明广义 lambda 捕获了工作。但是,当我们更改#if 1的代码时,我们得到了编译错误:

g++ -g -std=c++14 test01.cpp -o test01
In file included from test01.cpp:4:0:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional: In instantiation of 'static void std::_Function_base::_Base_manager<_Functor>::_M_clone(std::_Any_data&, const std::_Any_data&, std::false_type) [with _Functor = make_foo()::<lambda()>; std::false_type = std::integral_constant<bool, false>]':
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:1914:51:   required from 'static bool std::_Function_base::_Base_manager<_Functor>::_M_manager(std::_Any_data&, const std::_Any_data&, std::_Manager_operation) [with _Functor = make_foo()::<lambda()>]'
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:2428:19:   required from 'std::function<_Res(_ArgTypes ...)>::function(_Functor) [with _Functor = make_foo()::<lambda()>; <template-parameter-2-2> = void; _Res = void; _ArgTypes = ]'
test01.cpp:17:5:   required from here
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/functional:1878:34: error: use of deleted function 'make_foo()::<lambda()>::<lambda>(const make_foo()::<lambda()>&)'
    __dest._M_access<_Functor*>() =
                                  ^
test01.cpp:15:27: note: 'make_foo()::<lambda()>::<lambda>(const make_foo()::<lambda()>&)' is implicitly deleted because the default definition would be ill-formed:
     return [x=std::move(x)]() 
                           ^
test01.cpp:15:27: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]'
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/memory:81:0,
                 from test01.cpp:10:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.2/include/g++-v4/bits/unique_ptr.h:356:7: note: declared here
       unique_ptr(const unique_ptr&) = delete;
       ^
Makefile:2: recipe for target 'all' failed
make: *** [all] Error 1

现在,鉴于我们返回的函数包含std::unique_ptr,因此我们无法复制生成的std::function 是有道理的。但是,由于我们返回的是动态创建的 lambda 函数,这不应该是一个 r 值并且定义有效吗?基本上,有什么方法可以修复make_foo,我们仍然有一个std::unique_ptr 的广义lambda 捕获?

【问题讨论】:

你不能。 std::function 要求它包装的函数对象是 CopyConstructible 好吧,该死的。无论如何要返回一个 lambda 而不将其包装在 std::function 中?如果我在 main 中将此函数创建为 lambda 函数,一切正常:int main() auto x = std::make_unique &lt;int&gt; (3); auto make_foo = []() auto x = std::make_unique &lt;int&gt; (3); return [x=std::move(x)]() std::cout &lt;&lt; *x &lt;&lt; std::endl; ; ; auto foo = make_foo(); foo(); 【参考方案1】:

作为@T.C.在 cmets 中说,std::functionrequires 它包装的可调用对象是 CopyConstructible,而您的 lambda 不是因为 unique_ptr 数据成员。

您可以利用 C++14 的函数返回类型推导从 make_foo 返回 lambda 并避免将其包装在 std::function 中。

auto make_foo() 
    auto x = std::make_unique <int> (3);
    return [x=std::move(x)]() 
        std::cout << *x << std::endl;
    ;


make_foo()();  // prints 3

Live demo

【讨论】:

【参考方案2】:

std::function 是不必要的可复制的(你真的想多久复制一次std::function?)。由于类型擦除,您可以复制std::function 的事实意味着您存储在std::function 中的内容必须是可复制的,即使您从未这样做!

你可以很容易地实现一个只移动的std::function。获得良好的 QOI 更难——类似于 SSO(小字符串优化)的 SFO(小函数优化)是避免无用堆分配的好主意。

here 是名为task 的仅移动std::function 的草图。它没有 SFO。

仅当您需要将生成的 lambda 代码“类型未连接”到生成 lambda 的代码中存储时才需要。如果您不必这样做,您可以返回 auto 并公开您的实现,让 C++14 返回类型推导为您完成工作。

【讨论】:

std::function 必须是可复制的 - 尝试使用不可复制的函数进行函数式编程将是一种悲惨的、没有回报的体验。其他语言通过使用引用计数指针在后台处理此问题 - 在 C++ 中,您可以类似地使用 std::shared_ptr 而不是 std::unique_ptr 并修复此示例。

以上是关于如何从 C++14 中的广义 lambda 捕获返回包含 std::unique_ptr 的 std::function?的主要内容,如果未能解决你的问题,请参考以下文章

访问 C++14 lambda 捕获,如结构成员

g++ 不允许在 lambda 中通过引用对 const 对象进行广义捕获?

移动 lambda:一旦你移动捕获了只移动类型,如何使用 lambda? [复制]

c++11 lambdas 捕获他们不使用的变量吗?

第18课 捕获机制及陷阱

在 c++14 lambda 表达式中捕获和移动 unique_ptr