When we create a thread using std::thread
in C++, we often need to pass arguments to the function or callable object that the thread will execute. Understanding how to pass these arguments correctly is crucial for ensuring thread safety and avoiding unexpected behavior. Let’s explore the different ways to pass arguments to threads in C++.
1. Passing Arguments by Value
By default, arguments are passed to the thread function by value. This means that a copy of each argument is made when the thread is created, and the function inside the thread operates on those copies.
#include <iostream>
#include <thread>
void printSum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}
int main() {
int x = 10;
int y = 20;
// Pass arguments by value (default behavior)
std::thread t(printSum, x, y);
t.join();
return 0;
}
In this example, the values of x
and y
are copied when the thread t
is created, so the printSum
function inside the thread operates on copies of x
and y
. This is the simplest and safest way to pass arguments, especially if you don’t need to modify the original variables.
2.Passing Arguments by Reference
Passing arguments by reference allows the thread function to modify the original variables. To pass arguments by reference, you must use std::ref
or std::cref
to wrap the arguments. This is necessary because std::thread
always tries to make a copy of the arguments, and using std::ref
signals to pass a reference instead.
#include <iostream>
#include <thread>
#include <vector>
#include <algorithm>
#include <functional>
void modifyVector(std::vector<int>& vec) {
std::transform(vec.begin(), vec.end(), vec.begin(), [](int n) { return n * 2; });
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Pass argument by reference using std::ref
std::thread t(modifyVector, std::ref(numbers));
t.join();
// Display modified vector
for (int n : numbers) {
std::cout << n << " ";
}
std::cout << std::endl;
return 0;
}
Here, std::ref(numbers)
is used to pass the vector numbers
by reference. The modifyVector
function operates directly on the original vector, doubling each element. Without std::ref
, the thread would receive a copy of the vector, and changes would not affect the original.
3. Passing Multiple Arguments
We can can pass multiple arguments to a thread function, mixing different passing methods (by value, by reference, etc.). Each argument must be handled appropriately based on how you want it to be passed.
#include <iostream>
#include <thread>
#include <string>
#include <functional>
void complexFunction(int id, const std::string& name, std::vector<int>& data) {
std::cout << "Thread " << id << ": Hello, " << name << "!" << std::endl;
std::cout << "Data size: " << data.size() << std::endl;
}
int main() {
int id = 1;
std::string name = "Alice";
std::vector<int> data = {1, 2, 3, 4, 5};
// Create a thread passing multiple arguments
std::thread t(complexFunction, id, std::cref(name), std::ref(data));
t.join();
return 0;
}
Here, we pass id
by value, name
by constant reference using std::cref
, and data
by reference using std::ref
. This allows the thread function to handle each argument as needed, ensuring the correct behavior.
4.Passing Arguments Using Move Semantics
When dealing with large objects or resources that are expensive to copy, you may want to transfer ownership of the object to the thread function using move semantics. This is particularly useful when working with objects like std::unique_ptr
that cannot be copied.
#include <iostream>
#include <thread>
#include <memory>
void processUniquePtr(std::unique_ptr<int> ptr) {
std::cout << "Processing unique_ptr with value: " << *ptr << std::endl;
}
int main() {
auto myPtr = std::make_unique<int>(42);
// Pass argument using std::move to transfer ownership
std::thread t(processUniquePtr, std::move(myPtr));
t.join();
if (!myPtr) {
std::cout << "myPtr is now nullptr after moving." << std::endl;
}
return 0;
}
In this example, std::move(myPtr)
transfers ownership of the std::unique_ptr
to the processUniquePtr
function. After the thread has been created, myPtr
is set to nullptr
, indicating that ownership has been transferred.