如果lambda使用std :: move()捕获不可复制的对象,为什么它不可移动?

时间:2017-12-28 07:17:17

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

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

    @font-face {
  font-family: 'Glyphicons Halflings';

  src: url('../fonts/glyphicons-halflings-regular.eot');
  src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}

#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)] { }); } 的lambda中,我使用main()捕获局部变量s。在调用post()之前,std::move()必须已成功移动构建。

但是,在s2内,post()不能使用对该lambda类型的右值引用构造。

如果我删除,可以使用此右值引用构建fs2 = move(s)

为什么添加f会使lambda不可移动?

这里是尝试coliru的link

2 个答案:

答案 0 :(得分:6)

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

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

答案 1 :(得分:2)

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

通常:

  • lambda既可以复制又可以移动。
  • 如果lambda具有不可复制的捕获,则它将使lambda本身不可复制。这些对象可以包装在smart_pointer中,但可以在lambda捕获中移动(或复制-shared_ptr)。
  • 如果没有按值捕获,则闭包类型(lambda)通常是可复制且可移动的。
  • 如果所有由值对象捕获的闭包类型都是可复制且可移动非常量类型(例如,类似C的类型),则闭包类型将是可复制和可移动的。
    • 否则,如果存在按值捕获,则闭包类型的move构造函数将复制按值捕获的对象。
  • 如果按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只是将一个对象变成右值引用。