视觉工作室实现"移动语义"和"右值参考"

exp*_*ial 5 c++ rvalue-reference move-semantics c++11 visual-studio-2012

我遇到了关于c ++ 11并发(第3部分)的Youtube视频和以下代码,它们在视频中编译并生成正确的结果.

但是,我使用Visual Studio 2012得到了此代码的编译错误.编译器抱怨参数类型为toSin(list<double>&&).如果我将参数类型更改为list<double>&,则编译代码.

我的问题是从中返回move(list)的内容_tmain(),它是右值引用还是仅仅是引用?

#include "stdafx.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <list>
#include <algorithm>
using namespace std;

void toSin(list<double>&& list)
{
    //this_thread::sleep_for(chrono::seconds(1));
    for_each(list.begin(), list.end(), [](double & x)
    {
        x = sin(x);
    });

    for_each(list.begin(), list.end(), [](double & x)
    {
        int count = static_cast<int>(10*x+10.5);
        for (int i=0; i<count; ++i)
        {
            cout.put('*');
        }
        cout << endl;
    });
}    

int _tmain(int argc, _TCHAR* argv[])
{
    list<double> list;

    const double pi = 3.1415926;
    const double epsilon = 0.00000001;
    for (double x = 0.0; x<2*pi+epsilon; x+=pi/16)
    {
        list.push_back(x);
    }

    thread th(&toSin, /*std::ref(list)*/std::move(list));
    th.join();    

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Yak*_*ont 6

这似乎是MSVC2012中的一个错误.(以及快速检查,MSVC2013和MSVC2015)

thread 不直接使用完美转发,因为在原始线程中存储对数据的引用(临时或非临时)并在生成的线程中使用它将极易出错并且很危险.

相反,它将每个参数复制到decay_t<?>内部数据中.

错误是当它调用worker函数时,它只是将该内部副本传递给您的过程.相反,它应该内部数据移动到调用中.

这似乎没有在编译器版本19中修复,我认为是MSVC2015(没有经过仔细检查),基于此处编译代码

这是由于标准的措辞(它应该调用decay_t<F>with decay_t<Ts>...- 这意味着rvalue绑定,而不是lvalue绑定),并且因为在调用过程之后永远不会再次使用存储在线程中的本地数据(逻辑上它应该被视为过期数据,而不是持久数据).

这是一个解决方法:

template<class F>
struct thread_rvalue_fix_wrapper {
  F f;
  template<class...Args>
  auto operator()(Args&...args)
  -> typename std::result_of<F(Args...)>::type
  {
      return std::move(f)( std::move(args)... );
  }
};
template<class F>
thread_rvalue_fix_wrapper< typename std::decay<F>::type >
thread_rvalue_fix( F&& f ) { return {std::forward<F>(f)}; }
Run Code Online (Sandbox Code Playgroud)

然后

thread th(thread_rvalue_fix(&toSin), /*std::ref(list)*/std::move(list));
Run Code Online (Sandbox Code Playgroud)

应该管用.(在上面链接的MSVC2015在线编译器中测试)基于个人经验,它也应该在MSVC2013中工作.我不知道MSVC2012.


Ben*_*igt -1

返回的std::move确实是一个右值引用,但这并不重要,因为thread构造函数不对其参数使用完美转发。首先,它将它们复制/移动到新线程拥有的存储中。然后,在新线程内,使用副本调用所提供的函数。

由于副本不是临时对象,因此此步骤不会绑定到右值引用参数。

标准的内容 (30.3.1.2):

新的执行线程执行

INVOKE( DECAY_COPY(std::forward<F>(f)),  DECAY_COPY(std::forward<Args>(args))... )
Run Code Online (Sandbox Code Playgroud)

DECAY_COPY在构造线程中对调用 进行评估。

本条款中的多个地方DECAY_COPY(x)使用了该操作。所有这些使用都意味着调用函数decay_copy(x)并使用结果,其中decay_copy定义如下:

template <class T> decay_t<T> decay_copy(T&& v)
{ return std::forward<T>(v); }
Run Code Online (Sandbox Code Playgroud)

值类别丢失。

  • 但是等等,“DECAY_COPY”返回一个原始的“T”(没有引用,什么也没有)。使用原始“T”调用“INVOKE”是在绑定到右值上下文中调用它。它没有说“我将调用`DECAY_COPY`,存储结果,然后传递存储的结果”,它说“我将使用`DECAY_COPY`调用”。如果这个论点是对的,那么你的分析就颠倒了,MSVC有bug。这也是一个实际错误,因为存储在“thread”中的参数将永远不会再次使用,因此将它们绑定到左值非常量引用在概念上是错误的,而绑定到右值是正确的。 (4认同)
  • @benvoigt 它在哪个线程中执行与 INVOKE 的语义无关。想象一下该子句(不同的线程)不存在:INVOKE 的语义意味着什么(即哪些重载是有效的等)是否清楚?它发生在不同的线程中如何改变这一点?它不是。它使实现变得棘手,但它不允许 MSVC 所做的事情。它绑定到右值而不是左值都是标准所强制的,*并且*是正确的做法。 (2认同)