【问题标题】:Why is a lambda not-movable if it captures a not-copiable object using std::move()?如果 lambda 使用 std::move() 捕获不可复制的对象,为什么它是不可移动的?
【发布时间】:2018-06-08 18:03:44
【问题描述】:

这是我生成错误的示例代码:

#include <functional>
using namespace std;

struct S {
    S() = default;
    S(const S&) = delete;
    S(S&&) = default;
    S& operator=(const S&) = delete;
    S& operator=(S&&) = delete;
};

template <typename F>
void post(F&& func)
{
    function<void()> f{forward<F>(func)};
}

int main()
{
    S s;
    post([s2 = move(s)] { });
}

main() 的lambda 内部,我使用std::move() 捕获局部变量s。在调用 post() 之前,s2 必须已经移动构造成功。

但是,在 post() 内部,f 不能使用对此 lambda 类型的右值引用来构造。

如果我删除,s2 = move(s)f 可以用这个右值引用构造。

为什么添加s2 = move(s) 会导致 lambda 不可移动?

这里是link 尝试使用 coliru。

【问题讨论】:

  • @Lưu Vĩnh Phúc,请告诉我你为什么不喜欢我的问题。
  • 是什么让你觉得我不喜欢它?我只是为语法高亮添加了一个标签
  • 对不起,我误解了修订日志,并认为你给了问题-1。
  • 还有一个事实是 S 有构造函数,它接受 S 实例而不是 S 引用。我必须删除那些才能到达 lambda。
  • @SornelHaetir,怎么样?可以和S(const S&amp;) = delete共存吗?它们是哪些?

标签: c++ function lambda c++14 move


【解决方案1】:

您的 lambda 不会因为移动捕获而变得不可移动。但它确实变得不可复制,这是一个问题。

std::function 不支持将提供的仿函数移动到自身中,它总是进行复制。因此,不可复制的 lambda(和其他可调用对象)不能与 std::function 一起使用。这个限制的原因是标准要求std::function是可复制的,如果用不可复制的可调用对象初始化则无法实现。

【讨论】:

  • 我试过std::function&lt;void()&gt; f = []{}; auto f2 = move(f); f();f()std::bad_function_call。它是否反驳了您的说法“std::function 不支持将提供的函子移动到自身中,它总是复制。”?
  • @BenjiMizrahi 不。您将 将仿函数移动到 std::function移动 std::function 对象本身混淆了。 前者不是可能,而后者完全没问题(实际上 移动 存储的仿函数)。前者不可能的原因是标准要求std::function 是可复制的,如果它可以从不可复制的函子构造,则无法实现。
【解决方案2】:

问题不在于您的 lambda,而在于您的对象不可复制,因为 std::function 要求其对象是可复制的,编译器会抱怨。您几乎应该始终关注rule-of-zero

一般:

  • lambda 既可以是可复制的,也可以是可移动的。
  • 如果 lambda 具有不可复制的捕获,它会使 lambda 本身不可复制。这些对象可以包装在一个 smart_pointer 中,但可以在 lambda 捕获中移动(或复制 - shared_ptr)。
  • 如果没有按值捕获,则闭包类型(lambda)通常可以轻松复制和轻松移动。
  • 如果值对象捕获的所有对象都是可轻松复制且可轻松移动的非 const 类型(例如 C 类类型),则闭包类型将可轻松复制和轻松移动。
    • 否则,如果存在按值捕获,则闭包类型的移动构造函数将复制按值捕获的对象。
  • 如果有 const 对象的按值捕获,则捕获列表中的任何移动都会产生副本。
  • 如果 lambda 本身是 const,它永远不会移动,只会复制,甚至复制到其他 const lambda。

示例:

#include <iostream>
#include <type_traits>

struct S
{
    S() {
        std::cout << "ctor" << '\n';
    }
    ~S() noexcept {
        std::cout << "dtor" << '\n';
    }
    S(const S&) { 
        std::cout << "copy ctor\n";
    }
    S(S&&) noexcept noexcept {
        std::cout << "move ctor\n";
    }
    S& operator= (const S&) {
        std::cout << "copy aop\n";
    }
    S& operator= (S&&) noexcept {
        std::cout << "move aop\n";
    }
};

template <typename T>
void getTraits()
{
    std::cout << std::boolalpha
        << "trivially_copy_constructible? "
        << std::is_trivially_copy_constructible_v<T>
        << "\ntrivially_move_constructible? "
        << std::is_trivially_move_constructible_v<T> << '\n' ;
}

int main()
{
    S s ;
    const S cs;
    {
        std::cout << "capture by value\n" ;
        //auto closure = [s = std::move(s)] {} ; // S::move construct               // 1.
        //auto closure = [cs = std::move(cs)] {} ; // S::copy construct             // 2.
        //const auto closure = [s = std::move(s)] {} ; // S::move construct         // 3.
        const auto closure = [cs = std::move(cs)] {} ; // S::copy construct         // 4.
        getTraits<decltype(closure)>();

        const auto copy_constructed = std::move(closure);
        const auto move_constructed = std::move(closure);
    }

    {
        std::cout << "\ncapture by reference\n";
        const auto closure = [&s] {};
        getTraits<decltype(closure)>();
    }
}

一次取消注释 1、2、3、4 并检查输出。请记住 std::move 只是将对象转换为右值引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 2018-05-26
    • 1970-01-01
    • 2020-10-18
    • 2014-12-31
    相关资源
    最近更新 更多