为啥 std::move 不能与 std::list 一起使用

Posted

技术标签:

【中文标题】为啥 std::move 不能与 std::list 一起使用【英文标题】:Why std::move is not working with std::list为什么 std::move 不能与 std::list 一起使用 【发布时间】:2017-07-04 17:29:59 【问题描述】:

我无法编译下面的程序。

void toSin(std::list<double>&& list)

    std::for_each(list.begin(), list.end(), [](double& x)
    
        x = sin(x);
    );


int main()

    std::list<double> list;
    const double pi = 3.141592;
    const double epsilon = 0.0000001;
    for (double x = 0.0; x < 2 * pi + epsilon; x = x + pi / 16)
    
        list.push_back(x);
    
    // Start thread
    std::thread th(toSin, std::move(list));
    th.join();
    return 0;

我得到 > 错误 C2664: 'void (std::list&lt;double,std::allocator&lt;_Ty&gt;&gt; &amp;&amp;)' : 无法将参数 1 从 'std::list&lt;double,std::allocator&lt;_Ty&gt;&gt;' 转换为 'std::list&lt;double,std::allocator&lt;_Ty&gt;&gt; &amp;&amp;'

【问题讨论】:

无法复制。您使用的是哪个版本的视觉工作室?请注意,我添加了一堆缺失的标题。 std::thread th(toSin, std::move(list)); 行暗示你不应该在list 上迭代超过那个点,因为它已经被移走了。但是您尝试在下一行对其进行迭代。 Visual Studio 2013 2013 缺少很多 C++ 11 支持。在 2015 年编译,但不应该按照您想要的方式运行。看看能不能升级。还要注意@FrançoisAndrieux 的警告。 除了 FrançoisAndrieux 评论,如果您希望使用相同的列表,您将拥有 UB,因为并发访问不同步(互斥,...)。 【参考方案1】:

我觉得你的编译器在这里错了。衰减(复制)的值类型应该可绑定到右值引用。

随便看看this quote from the documentation

3) 创建新的 std::thread 对象并将其与执行线程相关联。新的执行线程开始执行

std::invoke(decay_copy(std::forward<Function>(f)), decay_copy(std::forward<Args>(args))...);

基本上,您作为参数传递给std::thread 的构造函数的任何内容都将作为函数参数复制到该函数。

还要知道,如果你让它按值而不是按右值引用接受std::list 变量,你的函数将正常工作。更多详情请见Correct usage of rvalue references as parameters


如果您的意图是将变量的引用传递给线程函数,我通常使用 lambda

std::list<double> lst;
auto th = std::thread[&lst]() 
    toSin(lst);
;

但是你也可以使用std::ref 来达到同样的效果。我只是个人觉得 lambda 方法更清晰。

std::list<double> lst;
auto th = std::threadtoSin, std::ref(lst);

Also as correctly pointed out in the comments,您的代码中有一个竞争条件,您应该使用mutex 来阻止它,或者等待线程完成

auto th = std::thread[&lst]() 
    toSin(lst);
;
th.join();

// then iterate and print out

【讨论】:

更新了上面的代码,我只是想给工作线程一个唯一的内存以避免同步问题。 @NARESHKITTUR 但是,如果您这样做,您打算稍后在主线程中打印出列表吗? 没错,我将无法在主线程中使用列表。我将不得不在工作线程本身中处理/打印列表。所以为了解决这个问题,我应该通过互斥体的引用传递列表。 @NARESHKITTUR 正确,或者等待线程通过.join()完成【参考方案2】:

我认为您可能会错过一些 #include,该代码适用于 Visual Studio 2015

#include <algorithm>
#include <list>
#include <thread>
void toSin(std::list<double>&& list)

    std::for_each(list.begin(), list.end(), [](double& x)
    
        x = sin(x);
    );


int main()

    std::list<double> list;
    const double pi = 3.141592;
    const double epsilon = 0.0000001;
    for (double x = 0.0; x < 2 * pi + epsilon; x = x + pi / 16)
    
        list.push_back(x);
    
    // Start thread
    std::thread th(toSin, std::move(list));
    th.join();
    return 0;

【讨论】:

Visual Studio 2015 不是 Visual Studio 2013。2013 在其 c++11 支持方面存在巨大漏洞。它不编译的可能性很大。 2013进不去,坑都填完看不到升级的意义,所以没副本试试。

以上是关于为啥 std::move 不能与 std::list 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

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

std::forward 与 std::move 的用法

std::move与std::forward

std::move与std::forward

std::move与std::forward

左值的 C++ std::move() 性能明智