C++ Reactive Programming
上QQ阅读APP看书,第一时间看更新

Passing arguments into a thread

So, we have figured out how to launch and wait over a thread. Now, let's see how to pass arguments into a thread initialization function. Let's look at an example to find the factorial of a number:

class Factorial 
{ 
private: 
    long long myFact; 
     
public: 
    Factorial() : myFact(1) 
    { 
    } 
     
    void operator() (int number) 
    { 
        myFact = 1; 
        for (int i = 1; i <= number; ++i) 
        { 
            myFact *= i; 
        } 
        std::cout << "Factorial of " << number << " is " << myFact; 
    } 
}; 
 
int main() 
{ 
    Factorial fact; 
     
    std::thread t1(fact, 10); 
     
    t1.join(); 
} 
 

From this example, it is clear that passing arguments into a thread function or a thread callable object can be achieved by passing additional arguments into an std::thread() declaration. One thing we must keep in mind; the arguments passed are copied into the thread's internal storage for further execution. It is important for a thread's execution to have its own copy of arguments, as we have seen the problems associated with local variables going out of scope. To discuss passing arguments into a thread further, let's go back to our first Hello World example from this chapter:

void thread_proc(std::string msg); 
 
std::thread t(thread_proc, "Hello World\n"); 

In this case, the thread_proc() function takes std::string as an argument, but we are passing a const char* as an argument to the thread function. Only in the case of a thread is the argument passed, converted, and copied into the thread's internal storage. Here, const char* will be converted to std::string. The type of argument supplied to a thread must be chosen while keeping this in mind. Let's see what happens if a pointer is supplied to the thread as an argument:

void thread_proc(std::string msg); 
void func() 
{ 
   char buf[512]; 
   const char* hello = "Hello World\n"; 
   std::strcpy(buf, hello); 
 
   std::thread t(thread_proc, buf); 
   t.detach(); 
} 

In the preceding code, the argument supplied to the thread is a pointer to the local variable buf. There is a probable chance that the func() function will exit before the conversion of buf to an std::string happens on the thread. This could lead to an undefined behavior. This problem can be resolved by casting the buf variable into std::string in the declaration itself, as follows:

std::thread t(thread_proc, std::string(buf)); 

Now, let's look at the cases where you want a reference to get updated in the thread. In a typical scenario, the thread copies the value supplied to the thread to ensure a safe execution, but the standard library has also provided a means to pass the argument by reference to a thread. In many practical systems, you might have seen that a shared data structure is getting updated inside a thread. The following example shows how to achieve pass by reference in a thread:

void update_data(shared_data& data);

void another_func() { shared_data data; std::thread t(update_data, std::ref(data)); t.join(); do_something_else(data); }

In the preceding code, wrapping the arguments passed into the std::thread constructor with std::ref ensures that the variable supplied inside the thread is referenced to the actual parameters. You might have noticed that the function prototype of the thread initialization function is accepting a reference to the shared_data object, but why do you still need an std::ref() wrapping for thread invocation? Consider the following code for thread invocation:

std::thread t(update_data, data);

In this case, the update_data() function expects the shared_data argument to be treated as a reference to actual parameters. But when used as a thread initialization function, arguments are simply copied internally. When the call to update_data() happens, it will pass a reference to the internal copies of arguments and not a reference to the actual parameters.