C++11 : How to use std::thread as a member variable in class ?

In this article we will discuss how to use std::thread object as member variable inside class and its benefits.

As std::thread objects are move only, therefore while designing a class that use std::thread as member variable, we need to take care that objects of this class should also be move only.

Creating Move-only class with std::thread as member variable

Let’s create a ThreadWrapper class that has std::thread as member variable and make it move-able by,

  • Deleting its copy constructor and assignment operator.
  • Defining Move constructor and Move assignment operator.
/*
 * A class that has thread object as member variable
 */
class ThreadWrapper
{
	// std::thread object
	std::thread  threadHandler;

public:
	//Delete the copy constructor
	ThreadWrapper(const ThreadWrapper&) = delete;

	//Delete the Assignment opeartor
	ThreadWrapper& operator=(const ThreadWrapper&) = delete;

	// Parameterized Constructor
	ThreadWrapper(std::function<void()> func);

	// Move Constructor
	ThreadWrapper(ThreadWrapper && obj);

	//Move Assignment Operator
	ThreadWrapper & operator=(ThreadWrapper && obj);

	//Destructor
	~ThreadWrapper();
};

Its parameterized constructor will accept callback / function pointer / funcion object as argument that will be used as thread function by internal thread object i.e.

// Parameterized Constructor
ThreadWrapper::ThreadWrapper(std::function<void()> func) : threadHandler(func)
{}

Move Constructor & Assignment Operator

In Move constructor and assignment operator we need to move the thread object i.e.

// Move Constructor
ThreadWrapper::ThreadWrapper(ThreadWrapper && obj) : threadHandler(std::move(obj.threadHandler))
{
	std::cout << "Move Constructor is called" << std::endl;
}

//Move Assignment Operator
ThreadWrapper & ThreadWrapper::operator=(ThreadWrapper && obj)
{
	std::cout << "Move Assignment is called" << std::endl;
	if (threadHandler.joinable())
		threadHandler.join();
	threadHandler = std::move(obj.threadHandler);
	return *this;
}

In Move assignment operator we first need to join the current thread  object if its joinable, before replacing it with new thread object.

In the destructor of ThreadWrapper we need to join the thread object. It is necessary because if thread object is destructed without joining, then it will terminate the application i.e.

// Destructor : Join the thread object
ThreadWrapper::~ThreadWrapper()
{
	if (threadHandler.joinable())
		threadHandler.join();
}

Now Let’s create a ThreadWrapper object, now when this object will be destructed, internal thread will be joined in destructor i.e

// Creating a std::function object
std::function<void()> func = []() {
	// Sleep for 1 second
	std::this_thread::sleep_for (std::chrono::seconds(1));
	// Print thread ID
	std::cout << "From Thread ID : " << std::this_thread::get_id() << "\n";
};

{
	// Create a ThreadWrapper object
	// It will internally start the thread
	ThreadWrapper wrapper(func);

	//When wrapper will go out of scope, its destructor will be called
	// Which will internally join the member thread object
}

Also, we can create a vector of ThreadWraper i.e.

// Create a vector of ThreadWrapper objects
std::vector<ThreadWrapper> vecOfThreads;
// Add ThreadWrapper objects in thread
ThreadWrapper thwp1(func);
ThreadWrapper thwp2(func);
vecOfThreads.push_back(std::move(thwp1));
vecOfThreads.push_back(std::move(thwp2));

We don’t even need to join the threads separately , it will be automatically joined when vector is destructed. We can also change the content of vector i.e.

ThreadWrapper thwp3(func);

// Change the content of vector
vecOfThreads[1] = std::move(thwp3);

The complete example is as follows,

#include <thread>
#include <mutex>
#include <vector>
#include <iostream>
#include <assert.h>
#include <chrono>

/*
 * A class that has thread object as member variable
 */
class ThreadWrapper
{
	// std::thread object
	std::thread  threadHandler;

public:
	//Delete the copy constructor
	ThreadWrapper(const ThreadWrapper&) = delete;

	//Delete the Assignment opeartor
	ThreadWrapper& operator=(const ThreadWrapper&) = delete;

	// Parameterized Constructor
	ThreadWrapper(std::function<void()> func);

	// Move Constructor
	ThreadWrapper(ThreadWrapper && obj);

	//Move Assignment Operator
	ThreadWrapper & operator=(ThreadWrapper && obj);

	//Destructor
	~ThreadWrapper();
};

// Parameterized Constructor
ThreadWrapper::ThreadWrapper(std::function<void()> func) : threadHandler(func)
{}

// Move Constructor
ThreadWrapper::ThreadWrapper(ThreadWrapper && obj) : threadHandler(std::move(obj.threadHandler))
{
	std::cout << "Move Constructor is called" << std::endl;
}

//Move Assignment Operator
ThreadWrapper & ThreadWrapper::operator=(ThreadWrapper && obj)
{
	std::cout << "Move Assignment is called" << std::endl;
	if (threadHandler.joinable())
		threadHandler.join();
	threadHandler = std::move(obj.threadHandler);
	return *this;
}

// Destructor
ThreadWrapper::~ThreadWrapper()
{
	if (threadHandler.joinable())
		threadHandler.join();
}

int main()
{
	// Creating a std::function object
	std::function<void()> func = []() {
		// Sleep for 1 second
		std::this_thread::sleep_for (std::chrono::seconds(1));
		// Print thread ID
		std::cout << "From Thread ID : " << std::this_thread::get_id() << "\n";
	};

	{
		// Create a ThreadWrapper object
		// It will internally start the thread
		ThreadWrapper wrapper(func);

		//When wrapper will go out of scope, its destructor will be called
		// Which will internally join the member thread object
	}

	// Create a vector of ThreadWrapper objects
	std::vector<ThreadWrapper> vecOfThreads;


	// Add ThreadWrapper objects in thread
	ThreadWrapper thwp1(func);
	ThreadWrapper thwp2(func);
	vecOfThreads.push_back(std::move(thwp1));
	vecOfThreads.push_back(std::move(thwp2));

	ThreadWrapper thwp3(func);

	// Change the content of vector
	vecOfThreads[1] = std::move(thwp3);


	//When vector will go out of scope, its destructor will be called, which will
	// internally call the destructor all ThreadWrapper objects , which in turn
	// joins the member thread object.

	return 0;
}

Output:

From Thread ID : 140646533629696
Move Constructor is called
Move Constructor is called
Move Constructor is called
Move Assignment is called
From Thread ID : 140646533629696
From Thread ID : 140646525236992
From Thread ID : 140646516844288

 

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top