How it works...

In this recipe, we will learn how to add std::mutex to a class's private members while still being able to handle const scenarios. Generally speaking, there are two ways to ensure an object is thread-safe. The first method is to place std::mutex at the global level. Doing this ensures an object can be passed as a constant reference or the object itself can have a function marked as const.

For this, consider the following code example:

#include <mutex>
#include <thread>
#include <iostream>

std::mutex m{};

class the_answer
{
public:
void print() const
{
std::lock_guard lock(m);
std::cout << "The answer is: 42 ";
}
};

int main(void)
{
the_answer is;
is.print();

return 0;
}

In the preceding example, we create an object that outputs to stdout when the print() function is executed. The print() function is labeled as const, which tells the compiler that the print() function will not modify any class members (that is, the function is read-only). Since std::mutex is global, the const-qualifier of the object is maintained and the code compiles and executes without an issue.

The problem with a global std::mutex object is that every instance of the object must use the same std::mutex object. This is fine if the user intends this, but what if you want each instance of the object to have its own std::mutex object (for example, when the same instance of the object might be executed by more than one thread)?

For this, let's take a look at how that happens using the following example:

#include <mutex>
#include <thread>
#include <iostream>

class the_answer
{
std::mutex m{};

public:
void print() const
{
std::lock_guard lock(m);
std::cout << "The answer is: 42 ";
}
};

int main(void)
{
the_answer is;
is.print();

return 0;
}

If we attempt to compile this, we get the following:

In the preceding example, all we did was take the previous example and move std::mutex inside the class as a private member. As a result, when we attempt to compile the class, we get a compiler error. This is because the print() function is marked as const, which tells the compiler that the print() function will not modify any of the class's members. The problem is that when you attempt to lock std::mutex, you must modify it, resulting in a compiler error.

To overcome this, we must tell the compiler to ignore this error by marking std::mutex as mutable. Marking a member as mutable tells the compiler that the member is allowed to be modified, even when the object is passed as a constant reference or when the object defines a constant function.

For example, this is how the code appears on const marked as mutable:

#include <mutex>
#include <thread>
#include <iostream>

class the_answer
{
mutable std::mutex m{};

public:
void print() const
{
std::lock_guard lock(m);
std::cout << "The answer is: 42 ";
}
};

int main(void)
{
the_answer is;
is.print();

return 0;
}

As you can see in the preceding example, once we mark std::mutex as mutable, the code compiles and executes as we would expect. It should be noted that std::mutex is one of the few examples for which the use of mutable is acceptable. The mutable keyword can easily be abused, resulting in code that doesn't compile or operate as expected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset