传递临时对象作为线程参数
要避免的陷阱1
- 线程入口的为函数,入口函数的形参为引用、指针
#include <iostream>
#include <thread>
using namespace std;
//定义函数,可调用的对象。作为线程参数的入口
void myPrint(const int &i, char *pmybuf){
cout << "形参 const int &i = " << i << endl; //&i = 0x1031e90 , *&i = 1
cout << "形参 char *pmybuf = " << pmybuf << endl; //&pmybuf = 0x284fcf8, *&pmybuf = 0x62fdf0,pmybuf作为一个指针变量,存放了实参的地址
}
}
int main(){
int mvar = 1; // &mvar = 0x62fe04, *&mvar = 1
int &mvary = mvar; //定义mvar的引用,是变量mvar的别名 &mvary = 0x62fe04, *&mvary = 1
char mybuf[] = "this is a test!"; //&mybuf = 0x62fdf0,
thread myThread(myPrint, mvar, mybuf); //定义myThread线程,函数myPrint作为线程参数的入口传入线程。mvar、mybuf作为函数myPrint的参数传入函数
myThread.join(); //主线程等待子线程结束
cout << "I Love China!" << endl;
return 0;
}
- 使用join的运行结果
- 使用detach的运行结果
结论1
- 引用形参i的地址和主线程中mvar的地址不一样,所以即使主线程提前结束释放了mvar的内存空间,子线程中的i仍然可以正常使用,可以使用detach
- 指针形参pmybuf的地址和主线程中mybuf的地址相同,所以如果主线程提前结束,释放了mybuf的地址空间,子线程中的pmybuf便不能正常使用,不可使用detach
要避免的陷阱2
- 在函数的形参传递时使用隐式类型转换,将char型字符数组转换成string
#include <iostream>
#include <thread>
using namespace std;
void myPrint2(const int &i, const string &pmybuf){
cout << "myPrint2 形参 const int &i = " << i << endl; //&i = 0xe91e90, *&i = 1
cout << "myPrint2 形参 string &pmybuf = " << pmybuf << endl; //&pmybuf = 0x27afce0
}
int main(){
int mvar = 1; // &mvar = 0x62fe04, *&mvar = 1
int &mvary = mvar; //定义mvar的引用,是变量mvar的别名 &mvary = 0x62fe04, *&mvary = 1
char mybuf[] = "this is a test!"; //&mybuf = 0x62fdf0,
thread myThread2(myPrint2, mvar, mybuf);
myThread2.join();
// myThread2.detach();
cout << "I Love China!" << endl;
return 0;
}
- 使用join运行结果
- 使用detach运行结果
- 定义一个类A来类比string类,线程函数入口进行隐式类型转换
class A{
public:
int m_i; //&m_i = 0x62fe0c, *&m_i = 0
A(int a):m_i(a) {
cout << "[A::A(int a)构造函数执行]" << endl;
cout << "类A的构造函数的线程id : " << std::this_thread::get_id() << endl;
} //类型转换构造函数,将整型a转换为类A的对象m_i; 将传入的i的值赋给m_i。 &i = 0x62fde8, *&i = 10
A(const A &a):m_i(a.m_i) {cout << "[A::A(const A)构造函数执行]" << endl;}
~A() {cout << "[A::~A()析构函数执行]" << endl;}
};
void myPrint3(const int &i, const A& mysecond){ //函数的形参为A类型的引用pmybuf
cout << "myPrint3 形参 const int &i = " << i << endl;
cout << "myPrint3 形参 const A& pmybuf = " << mysecond.m_i << endl;
cout << "线程入口myPrint3的线程id为: " << std::this_thread::get_id() << endl;
}
int main(){
int mvar = 1;
int mysecond = 12;
thread myThread3(myPrint3, mvar, mysecond); //第一个参数为函数,作为线程的入口;第二个参数为类A的对象,作为函数myPrint3的实参
myThread3.join();
cout << "主线程的id是: " << std::this_thread::get_id() << endl;
cout << "I Love China!" << endl;
return 0;
}
- 类A的构造函数(隐式类型转换是在子线程中完成的)
- 在线程传参时,在线程参数传入时进行类型转换
class A{
public:
int m_i; //&m_i = 0x62fe0c, *&m_i = 0
A(int a):m_i(a) {
cout << "[A::A(int a)构造函数执行]" << endl;
cout << "类A的构造函数的线程id : " << std::this_thread::get_id() << endl;
} //类型转换构造函数,将整型a转换为类A的对象m_i; 将传入的i的值赋给m_i。 &i = 0x62fde8, *&i = 10
A(const A &a):m_i(a.m_i) {
cout << "[A::A(const A)拷贝构造函数执行]" << endl;
cout << "类A的拷贝构造函数的线程id : " << std::this_thread::get_id() << endl;
}
~A() {
cout << "[A::~A()析构函数执行]" << endl;
cout << "类A的析构函数的线程id : " << std::this_thread::get_id() << endl;
}
};
void myPrint3(const int &i, const A &mysecond){ //函数的形参为A类型的引用pmybuf
cout << "myPrint3 形参 const int &i = " << i << endl;
cout << "myPrint3 形参 const A& pmybuf = " << mysecond.m_i << endl;
// cout << "myPrint3 形参 const A& pmybuf 的地址 &pmybuf 为 = " << &mysecond << endl;
cout << "线程入口myPrint3的线程id为: " << std::this_thread::get_id() << endl;
}
int main(){
int mvar = 1;
int mysecond = 12;
thread myThread3(myPrint3, mvar,A(mysecond)); //第一个参数为函数,作为线程的入口;第二个参数为类A的对象,作为函数myPrint3的实参,在向线程传参时进行类型转换
myThread3.join();
cout << "主线程的id是: " << std::this_thread::get_id() << endl;
cout << "I Love China!" << endl;
return 0;
}
- 在主线程中进行类型转换
- 如果在函数入口不使用引用
void myPrint3(const int &i, const A mysecond)
则会多进行一次拷贝构造
结论2
- 函数形参传递的过程中使用隐式类型转换使得,mybuf和pmybuf的地址不同,这样即使主线程先执行完,释放了mybuf的地址空间,子线程中的pmybuf也能正常使用。
- 虽然隐式类型转换的pmybuf和mybuf的地址不同,但是主线程在完成类型转换之前就将mybuf的地址释放了(即隐式类型转换是在子线程中完成的而不是在主线程中完成的),所以子线程中的pmybuf仍然不能正常使用。
- 隐式类型转换是在子线程中完成的,如果主线程提前结束,子线程中仍然不能使用到主线程的变量值
结论
- 传递int这种简单的类型,建议使用值传递
- 如果传递类对象则避免使用隐式类型转换,在函数形参中,尽量使用引用来接,否则还会创建出一个新的对象
- 终极结论:不建议使用detach()
临时对象作为线程参数
- 在子线程中修改参数
class A{
public:
mutable int m_i; //mutable表示可以修改。&m_i = 0x62fe0c, *&m_i = 0
A(int a):m_i(a) {
cout << "[A::A(int a)构造函数执行]" << endl;
cout << "类A的构造函数的线程id : " << std::this_thread::get_id() << endl;
} //类型转换构造函数,将整型a转换为类A的对象m_i; 将传入的i的值赋给m_i。 &i = 0x62fde8, *&i = 10
A(const A &a):m_i(a.m_i) {
cout << "[A::A(const A)拷贝构造函数执行]" << endl;
cout << "类A的拷贝构造函数的线程id : " << std::this_thread::get_id() << endl;
}
~A() {
cout << "[A::~A()析构函数执行]" << endl;
cout << "类A的析构函数的线程id : " << std::this_thread::get_id() << endl;
}
};
- 线程入口函数虽然为引用,但是在子线程中修改参数并不会影响主线程
- 类A中声明对象时使用mutable表示可修改
class A{
public:
mutable int m_i; //mutable表示可以修改。
A(int a):m_i(a) {
cout << "[A::A(int a)构造函数执行]" << endl;
cout << "类A的构造函数的线程id : " << std::this_thread::get_id() << endl;
} //类型转换构造函数,将整型a转换为类A的对象m_i; 将传入的i的值赋给m_i。 &i = 0x62fde8, *&i = 10
A(const A &a):m_i(a.m_i) {
cout << "[A::A(const A)拷贝构造函数执行]" << endl;
cout << "类A的拷贝构造函数的线程id : " << std::this_thread::get_id() << endl;
}
~A() {
cout << "[A::~A()析构函数执行]" << endl;
cout << "类A的析构函数的线程id : " << std::this_thread::get_id() << endl;
}
};
void myPrint4(const A &pmybuf){ // &pmybuf = 0x7a1e88
pmybuf.m_i = 100;
cout << "子线程myPrint4的参数const A &pmybuf的地址 = " << &pmybuf << "\nthread id = " << std::this_thread::get_id() << endl;
}
int main(){
A myObj(10); //创建一个A类对象myObj,将10作为实参传入函数A(int i) &myObj = 0x62fe0c mi = 0 ,10
thread myThread4(myPrint4, myObj);
cout << "主线程的id是: " << std::this_thread::get_id() << endl;
cout << "I Love China!" << endl;
return 0;
}
- 线程入口函数中&pmybuf = 0x7a1e88
- 主线程中 &myObj = 0x62fe0c
- 地址不同,修改子线程中参数并不会影响主线程
thread myThread4(myPrint4, std::ref(myObj));
- 使用std::ref会生成一个真引用,使得子线程中pmybuf的地址和主线程中地址相同
使用智能指针作为参数
//在主线程中构建智能指针
//使用std::move()将智能指针传入线程函数的形参,使得子线程的智能指针形参和主线程的智能指针指向同一地址
unique_ptr<int> mya (new int(2) );
thread myThread4(myPrint5, std::move(mya));
//使用智能指针作为参数
void myPrint5(unique_ptr<int> a)
- 如果主线程执行结束释放了智能指针的内存,则子线程中的智能指针形参可能会出错
使用成员函数指针作为线程入口函数
//在类A中定义成员函数void thread_work
void thread_work(int n){
cout << "类A的成员函数作为线程函数的入口,线程id = " << std::this_thread::get_id() << endl;
cout << "类A的成员函数的this指针 = " << this << endl;
}
//在主线程中
A myobj(10);
thread mytobj(&A::thread_work, myobj, 5); //第一个参数为类A的成员函数的地址作为线程的入口,第二个参数为类A的对象,第三个参数为线程入口成员函数的形参
mytobj.join();
- 首先执行 A myobj(10);构造一个类A对象
- 在主线程中执行拷贝构造函数,对myobj进行拷贝构造,所以即使使用detach()也不会出问题
- 在主线程中进行拷贝,在子线程中进行析构