构造函数的参数
std::thread
类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数。
第一参数的类型并不是c
语言中的函数指针(c
语言传递函数都是使用函数指针),在c++11
中,增加了可调用对象(Callable Objects)的概念,总的来说,可调用对象可以是以下几种情况:
- 函数指针
- 重载了
operator()
运算符的类对象,即仿函数 lambda
表达式(匿名函数)std::function
函数指针示例
1 | // 普通函数 无参 |
实验的时候还发现一个问题,如果将重载的函数作为线程的入口函数,会发生编译错误!编译器搞不清楚是哪个函数,如下面的代码:
1 | // 普通函数 无参 |
仿函数
1 | // 仿函数 |
一个仿函数类生成的对象,使用起来就像一个函数一样,比如上面的对象f
,当使用f()
时就调用operator()
运算符。所以也可以让它成为线程类的第一个参数,如果这个仿函数有参数,同样的可以写在线程类的后几个参数上。
而t2
之所以编译错误,是因为编译器并没有将Fctor()
解释为一个临时对象,而是将其解释为一个函数声明,编译器认为你声明了一个函数,这个函数不接受参数,同时返回一个Factor
对象。解决办法就是在Factor()
外包一层小括号()
,或者在调用std::thread
的构造函数时使用{}
,这是c++11
中的新的同意初始化语法。
但是,如果重载的operator()
运算符有参数,就不会发生上面的错误。
匿名函数
1 | std::thread t1([](){ |
std::function
1 | class A{ |
传值还是引用
先提出一个问题:如果线程入口函数的的参数是引用类型,在线程内部修改该变量,主线程的变量会改变吗?
代码如下:
1 |
|
事实上,该代码使用g++
编译会报错,而使用vs2015
并不会报错,但是子线程并没有成功改变外面的变量m
。
我是这么认为的:std::thread
类,内部也有若干个变量,当使用构造函数创建对象的时候,是将参数先赋值给这些变量,所以这些变量只是个副本,然后在线程启动并调用线程入口函数时,传递的参数只是这些副本,所以内部怎么操作都是改变副本,而不影响外面的变量。g++
可能是比较严格,这种写法可能会导致程序发生严重的错误,索性禁止了。
而如果可以想真正传引用,可以在调用线程类构造函数的时候,用std::ref()
包装一下。如下面修改后的代码:
1 | std::thread t1(f, std::ref(m)); |
然后vs
和g++
都可以成功编译,而且子线程可以修改外部变量的值。
当然这样并不好,多个线程同时修改同一个变量,会发生数据竞争。
同理,构造函数的第一个参数是可调用对象,默认情况下其实传递的还是一个副本。
1 |
|
对于线程t1
来说,内部调用的线程函数其实是一个副本,所以如果在函数内部修改了类成员,并不会影响到外面的对象。只有传递引用的时候才会修改。所以在这个时候就必须想清楚,到底是传值还是传引用!
线程对象只能移动不可复制
线程对象之间是不能复制的,只能移动,移动的意思是,将线程的所有权在std::thread
实例间进行转移。
1 | void some_function(); |
作者:StormZhu