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)
这似乎是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):
新的执行线程执行
Run Code Online (Sandbox Code Playgroud)INVOKE( DECAY_COPY(std::forward<F>(f)), DECAY_COPY(std::forward<Args>(args))... )
DECAY_COPY在构造线程中对调用 进行评估。
和
本条款中的多个地方
DECAY_COPY(x)使用了该操作。所有这些使用都意味着调用函数decay_copy(x)并使用结果,其中decay_copy定义如下:Run Code Online (Sandbox Code Playgroud)template <class T> decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }
值类别丢失。