C++并发编程(2):向线程函数传递参数

向线程函数传递参数

主要分为3种情况

  1. 普通函数
  2. 类的成员函数
  3. 参数为智能指针

需要特别注意,线程具有内部存储空间,参数会按默认方式复制为临时变量,然后再将该临时变量作为右值传给新线程上的函数或可调用对象

普通函数传参

#include <iostream>
#include <thread>
#include <string.h>
using namespace std;

// void myPrint(string& t_)
// void myPrint(const string &t_)
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, ref(text));
    thread t(myPrint, text);
    t.join();
    // t.detach();

		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_)
    // void printtext(const 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(A1);
    // thread t(&A::printtext,&A1,text);
    thread t(&A::printtext,ref(text));
    t.join();
    // t.detach();

    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 构造函数 。。。          //main中A1的构造
A 拷贝构造函数 。。。      //拷贝构造一个临时对象
A 拷贝构造函数 。。。      //将该临时对象拷贝到printtext函数中,作为该函数内的局部变量
A 析构函数 。。。          //临时对象使用完毕立即释放
t_ location:0x559be66f7288
t_:Ouch!
A 析构函数 。。。          //printtext函数运行结束释放栈区上的数据
更改后text:Hello C++!
main end!
A 析构函数 。。。          //mian结束调用A1的析构

如果保留第二个参数的&运行输出如下

text location:0x7ffe6078fd40
buff location:0x7ffe6078fd6d
更改前text:Hello C++!
A 构造函数 。。。
t_ location:0x55af5cc36288
t_:Ouch!
更改后text:Hello C++!
main end!
A 析构函数 。。。

可以看到只在主函数中构造了一次,而免去了两次拷贝,提高了运行效率

因此建议同时传入函数指针和对象指针

这里同样要注意参数的真假引用问题

参数为智能指针

智能指针有三种:

  1. 独占指针——unique_ptr
  2. 共享指针——shared_ptr
  3. 弱指针——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(string& t_)
// void myPrint(const string &t_)
// void myPrint(string&& t_)
void myPrint(unique_ptr<string> t_)
//void myPrint(shared_ptr<string> t_)
{
    
    
    *t_ = "Ouch!";
    cout << "t_ location:" << &t_ << endl;
    cout << "myPrint:" << *t_ << endl;
}

int main()
{
    
    
    // string text = "Hello C++!";
    // unique_ptr<string> text(new string);
    unique_ptr<string> text = make_unique<string>("Hello C++!");
    // shared_ptr<string> text = make_shared<string>("Hello C++!");
    cout << "text location:" << &text << endl;
    cout << "更改前text:" << *text << endl;

    thread t(myPrint,move(text));
    t.join();
    // t.detach();

    // cout << "更改后text:" << *text << endl;
    
    cout << "main end!" << endl;

    return 0;
}

运行输出:

text location:0x7ffd55a3c1e8
更改前text:Hello C++!
t_ location:0x7f8bb48e8dc0
myPrint:Ouch!
main end!

注意点:

1、利用move函数向线程转移动态对象的归属权,转移后text变为NULL指针,因此要注释

// cout << "更改后text:" << *text << endl;

否则运行后会提示段错误,核心已转储

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(string& t_)
// void myPrint(const string &t_)
// void myPrint(string&& t_)
// void myPrint(unique_ptr<string> t_)
void myPrint(shared_ptr<string> t_)
{
    
    
    *t_ = "Ouch!";
    cout << "t_ location:" << &t_ << endl;
    cout << "myPrint:" << *t_ << endl;
}

int main()
{
    
    
    // string text = "Hello C++!";
    // unique_ptr<string> text(new string);
    // unique_ptr<string> text = make_unique<string>("Hello C++!");
    shared_ptr<string> text = make_shared<string>("Hello C++!");
    cout << "text location:" << &text << endl;
    cout << "更改前text:" << *text << endl;

    thread t(myPrint, text);
    // thread t(myPrint,move(text));
    t.join();
    // t.detach();

    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(string& t_)
    // void printtext(const string& t_)
    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()
{
    
    
    // string text = "Hello C++!";
    // unique_ptr<string> text(new string);
    // unique_ptr<string> text = make_unique<string>("Hello C++!");
    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++的强大

至于原因可能涉及到比较底层的逻辑,大家有兴趣可以自己学习了解下

猜你喜欢

转载自blog.csdn.net/Solititude/article/details/131738709