在使用 Visual Studio 上写跨平台 c++,经常会遇到一些问题,在 windows 下没有问题, 换个平台编都编不过,这次遇到的问题是有关线程库构造函数的参数传递
我们来看一个简单的程序
#include <cstdio>
#include <thread>
struct A
{
int i;
};
void add_one(A & r)
{
++r.i;
printf("now i is %d\n", r.i);
}
int main()
{
A a{ 9 };
printf("i is %d\n", a.i);
std::thread t(add_one, a);
t.join();
printf("i after thread: %d", a.i);
return 0;
}
这个程序在 msvc 下编译运行的结果如下
i is 9
now i is 10
i after thread: 9
很显然, msvc 复制了一份 a ,然后把它传递给新创建的线程,所以线程结束后,
主线程中的 a 完全没有变化。但是这份代码在 gcc 上是编译不过的,因为 add_one
接受的是引用,而我们传入了一个变量,
于是在最终 invoke 的步骤时没有找到 invoke 正确的实现,不过不同的 g++ 编译器,
报的错误也不近相同:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1/thread:342:5: error:
attempt to use a deleted function
__invoke(_VSTD::move(_VSTD::get<1>(__t)), _VSTD::move(_VSTD::get<_In...
^
这应该是匹配到了 invoke 的空实现
__nat __invoke(__any, ...);
/usr/include/c++/4.8.2/functional:1697:61: error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
typedef typename result_of<_Callable(_Args...)>::type result_type;
^
/usr/include/c++/4.8.2/functional:1727:9: error: no type named ‘type’ in ‘class std::result_of<void (*(int))(int&)>’
_M_invoke(_Index_tuple<_Indices...>)
result_type 中没有 type 类型,所以 gcc 也没有处理这种类型的参数
解决这个编译错误有两种方法:
add_one
参数改为变量,这样输出结果和 msvc 相同std::thread t(add_one, std::ref(a));
指明使用引用 ,这样输出结果如下i is 9
now i is 10
i after thread: 10
其实 msvc 针对引用参数也是做了一定防范的,如果我们把这段代码修改一下,去掉结构体,
直接使用基本类型 int
的话,代码会变成这样
#include <cstdio>
#include <thread>
void add_one(int & r)
{
++r;
printf("now i is %d\n", r);
}
int main()
{
int i = 9;
printf("i is %d\n", i);
std::thread t(add_one, i);
t.join();
printf("i after thread: %d\n", i);
return 0;
}
这时编译, msvc 就会报错了
1>c:\program files (x86)\microsoft visual studio 14.0\vc\include\thr\xthread(240): error C2672: “std::invoke”: 未找到匹配的重载函数
所以 msvc 没有严格遵守或实现这个规则,原因就不得而知了,不过我有点好奇如果我传递引用的话, 结果会是怎样,于是让我们把程序改成
#include <cstdio>
#include <thread>
struct A
{
int i;
};
void add_one(A & r)
{
++(r.i);
printf("now i is %d\n", r.i);
}
int main()
{
A a{ 9 };
A & r = a;
printf("i is %d\n", a.i);
std::thread t(add_one, r);
t.join();
printf("i after thread: %d\n", a.i);
return 0;
}
主线程的 i 在完成时依然是 9,不知道你猜对了没有,顺带一提这段代码在 g++ 上依然编不过, 但是当我们改成传递指针的话
#include <cstdio>
#include <thread>
struct A
{
int i;
};
void add_one(A * r)
{
++(r->i);
printf("now i is %d\n", r->i);
}
int main()
{
A a{ 9 };
A * r = &a;
printf("i is %d\n", a.i);
std::thread t(add_one, r);
t.join();
printf("i after thread: %d\n", a.i);
return 0;
}
i 最终会变成 10,另外 g++ 也可以正常编译并得到相同的结果了
对于 运行函数传递引用参数 的线程来说,传递变量时要加 std::ref , 不然在 msvc 下虽然编译没有问题,但是会造成变量值的变化不合预期