向线程函数传递参数
主要分为3种情况
- 普通函数
- 类的成员函数
- 参数为智能指针
需要特别注意,线程具有内部存储空间,参数会按默认方式复制为临时变量,然后再将该临时变量作为右值传给新线程上的函数或可调用对象
普通函数传参
#include <iostream>
#include <thread>
#include <string.h>
using namespace std;
void myPrint(string&& t_)
{
t_ = "Ouch!";
cout << "t_ location:" << &t_ << endl;
cout << "myPrint:" << t_ << endl;
}
int main()
{
char buff[]="Watch out!";
string text = "Hello C++!";
cout << "text location:" << &text << endl;
cout << "buff location:" << &buff << endl;
cout << "更改前text:" << text << endl;
thread t(myPrint, text);
t.join();
cout << "更改后text:" << text << endl;
cout << "main end!" << endl;
return 0;
}
注意点:
1、因为是以右值传递给函数,因此下列普通的引用是不可行的,编译不会通过
void myPrint(string& t_)
因此只能用另外两种引用方式
2、参数在传递前已经进行了一次拷贝,因此引用取的是临时变量的地址,函数里的更改并不会影响实参,运行上面的程序,输出
text location:0x7ffd5d2f84d0
buff location:0x7ffd5d2f84fd
更改前text:Hello C++!
t_ location:0x55dbf3e3e288
myPrint:Ouch!
更改后text:Hello C++!
main end!
发现地址不同且text的内容并没有更改,因此可以理解为假引用
如果需要以真引用的方式传递参数,则要用std::ref( )函数加以包装,这与std::bind( )函数类似,如下:
thread t(myPrint, ref(text));
运行后输出
text location:0x7ffcaaa3e6f0
buff location:0x7ffcaaa3e71d
更改前text:Hello C++!
t_ location:0x7ffcaaa3e6f0
myPrint:Ouch!
更改后text:Ouch!
main end!
注意此时的函数声明为
void myPrint(string& t_)
此时是我们正常理解的左值引用
类的成员函数传参
又可以分为两种情况,由该成员函数是否为静态成员函数为依据
1、静态成员函数
#include <iostream>
#include <thread>
#include <string.h>
using namespace std;
class A
{
public:
A(int a):m_a(a)
{
cout << "A 构造函数 。。。" << endl;
}
A(const A& a):m_a(a.m_a)
{
cout << "A 拷贝构造函数 。。。" << endl;
}
void operator()(){}
static void printtext(string& t_)
{
t_ = "Ouch!";
cout << "t_ location:" << &t_ << endl;
cout << "t_:" << t_ << endl;
}
~A(){cout << "A 析构函数 。。。" << endl;}
public:
int m_a;
};
int main()
{
char buff[]="Watch out!";
string text = "Hello C++!";
cout << "text location:" << &text << endl;
cout << "buff location:" << &buff << endl;
cout << "更改前text:" << text << endl;
A A1(5);
thread t(&A::printtext,ref(text));
t.join();
cout << "更改后text:" << text << endl;
cout << "main end!" << endl;
return 0;
}
因为静态成员函数在内存中位于全局区,属于类而不属于类的对象,因此注明该函数在该类的作用域下即可
thread t(&A::printtext,ref(text));
此时取地址符没有影响,下面也是可行的
thread t(A::printtext,ref(text));
输出如下:
text location:0x7ffdda801e90
buff location:0x7ffdda801ebd
更改前text:Hello C++!
t_ location:0x7ffdda801e90
t_:Ouch!
更改后text:Ouch!
main end!
2、非静态成员函数
此时若要将某个类的成员函数设定为线程函数,则应传入一个函数指针指向该成员函数,此外还要给出对象指针,如下:
thread t(&A::printtext,&A1,text);
有一个非常值得注意的地方,有两个取地址符,若去掉第一个,报错
/home/prejudice/Cplus_learning/src/thread_02.cpp:54:17: error: invalid use of non-static member function ‘void A::printtext(std::__cxx11::string&&)’
thread t(A::printtext,&A1,text);
因此第一个取地址符必须保留,那么看第二个
若去掉第二个参数的&,运行输出如下:
text location:0x7ffd84776370
buff location:0x7ffd8477639d
更改前text:Hello C++!
A 构造函数 。。。
A 拷贝构造函数 。。。
A 拷贝构造函数 。。。
A 析构函数 。。。
t_ location:0x559be66f7288
t_:Ouch!
A 析构函数 。。。
更改后text:Hello C++!
main end!
A 析构函数 。。。
为什么会有三次构造和析构呢,我是这样理解的
text location:0x7ffd84776370
buff location:0x7ffd8477639d
更改前text:Hello C++!
A 构造函数 。。。
A 拷贝构造函数 。。。
A 拷贝构造函数 。。。
A 析构函数 。。。
t_ location:0x559be66f7288
t_:Ouch!
A 析构函数 。。。
更改后text:Hello C++!
main end!
A 析构函数 。。。
如果保留第二个参数的&运行输出如下
text location:0x7ffe6078fd40
buff location:0x7ffe6078fd6d
更改前text:Hello C++!
A 构造函数 。。。
t_ location:0x55af5cc36288
t_:Ouch!
更改后text:Hello C++!
main end!
A 析构函数 。。。
可以看到只在主函数中构造了一次,而免去了两次拷贝,提高了运行效率
因此建议同时传入函数指针和对象指针
这里同样要注意参数的真假引用问题
参数为智能指针
智能指针有三种:
- 独占指针——unique_ptr
- 共享指针——shared_ptr
- 弱指针——weak_ptr
这里只讲独占指针和共享指针做参数,因为弱指针在目前读过的代码里见得实在很少
大家可以先补一下知识,在看下面的内容
http://c.biancheng.net/view/1478.html
unique_ptr为参数
#include <iostream>
#include <thread>
#include <string.h>
#include <memory>
using namespace std;
void myPrint(shared_ptr<string> t_)
{
*t_ = "Ouch!";
cout << "t_ location:" << &t_ << endl;
cout << "myPrint:" << *t_ << endl;
}
int main()
{
unique_ptr<string> text = make_unique<string>("Hello C++!");
cout << "text location:" << &text << endl;
cout << "更改前text:" << *text << endl;
thread t(myPrint,move(text));
t.join();
cout << "main end!" << endl;
return 0;
}
运行输出:
text location:0x7ffd55a3c1e8
更改前text:Hello C++!
t_ location:0x7f8bb48e8dc0
myPrint:Ouch!
main end!
注意点:
1、利用move函数向线程转移动态对象的归属权,转移后text变为NULL指针,因此要注释
否则运行后会提示段错误,核心已转储
2、make_unique是C++14标准里的,因此要更改CMakeLists.txt
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14 -pthread")
否则编译时会报错
error: ‘make_unique’ was not declared in this scope
unique_ptr<string> text = make_unique<string>();
shared_ptr为参数
#include <iostream>
#include <thread>
#include <string.h>
#include <memory>
using namespace std;
void myPrint(shared_ptr<string> t_)
{
*t_ = "Ouch!";
cout << "t_ location:" << &t_ << endl;
cout << "myPrint:" << *t_ << endl;
}
int main()
{
shared_ptr<string> text = make_shared<string>("Hello C++!");
cout << "text location:" << &text << endl;
cout << "更改前text:" << *text << endl;
thread t(myPrint, text);
t.join();
cout << "更改后text:" << *text << endl;
cout << "main end!" << endl;
return 0;
}
因为shared_ptr指针都指向同一块地址,故在main中text仍能打印输出
text location:0x7fffc22c9410
更改前text:Hello C++!
t_ location:0x7fd464a9bdc0
myPrint:Ouch!
更改后text:Ouch!
main end!
同样用类的成员函数进行测试
#include <iostream>
#include <thread>
#include <string.h>
#include <memory>
using namespace std;
class A
{
public:
A(int a) :m_a(a)
{
cout << "A 构造函数 。。。" << endl;
}
A(const A& a) :m_a(a.m_a)
{
cout << "A 拷贝构造函数 。。。" << endl;
}
void operator()() {}
void printtext(shared_ptr<string> t_)
{
*t_ = "Ouch!";
cout << "t_ location:" << &t_ << endl;
cout << "t_:" << *t_ << endl;
}
~A() { cout << "A 析构函数 。。。" << endl; }
public:
int m_a;
};
int main()
{
shared_ptr<string> text = make_shared<string>("Hello C++!");
cout << "text location:" << &text << endl;
cout << "更改前text:" << *text << endl;
A A1(5);
thread t(&A::printtext, &A1, text);
t.join();
cout << "更改后text:" << *text << endl;
cout << "main end!" << endl;
return 0;
}
打印输出:
text location:0098FC38
更改前text:Hello C++!
A 构造函数 。。。
t_ location:00AFFCC4
t_:Ouch!
更改后text:Ouch!
main end!
A 析构函数 。。。
注意到无论是普通函数还是成员函数t_ location与text location都不相同,最后text居然还能成功修改,不得不感叹C++的强大
至于原因可能涉及到比较底层的逻辑,大家有兴趣可以自己学习了解下
|