Introduction
There are times where modification inside const
member function must be done (for example, to allow for caching or memoization). The mutable
keyword and const_cast
are well-known in the C++ circles to work around these. The 3rd way is to use a const
pointer; we cannot modify the const
pointer but we can modify the contents it pointed to. Let’s dive in and discover for ourselves.
mutable Keyword
We will do a quick recap on the mutable
keyword. A lock have to be acquired on mutex. So by declaring the mutex
mutable
, it can be used inside a const
function.
class MyClass1 { public: MyClass1(int value) : m_Value(value) {} int GetValue() const { std::lock_guard<std::mutex> lock(m_Mutex); return m_Value; } private: int m_Value; mutable std::mutex m_Mutex; }; int GetValue(const MyClass1& c) { return c.GetValue(); }
Cast Away Constness
Next example, we will declare the member function as non-const
and do a const_cast()
. Typically C++ developers who do not know the mutable
keyword will use this technique.
class MyClass2 { public: MyClass2(int value) : m_Value(value) {} int GetValue() // not const { std::lock_guard<std::mutex> lock(m_Mutex); return m_Value; } private: int m_Value; std::mutex m_Mutex; }; int GetValue(const MyClass2& c) { MyClass2& c2 = const_cast(c); // cast away the constness return c2.GetValue(); }
Const Pointer to Member
As advertised, the data member can be modified though the member pointer.
class MyClass3 { public: MyClass3(int value) : m_Value(value), m_pMutex(&m_Mutex) {} int GetValue() const { std::lock_guard<std::mutex> lock(*m_pMutex); return m_Value; } private: int m_Value; std::mutex m_Mutex; std::mutex* m_pMutex; }; int GetValue(const MyClass3& c) { return c.GetValue(); }
Local Pointer to Member Does Not Compile
If local pointer is used, compiler will complain “error C2440: 'initializing': cannot convert from 'const std::mutex *' to 'std::mutex *
‘”.
// cannot work. class MyClass4 { public: MyClass4(int value) : m_Value(value) {} int GetValue() const { std::mutex* pMutex = &m_Mutex; // compiler complain std::lock_guard<std::mutex> lock(*pMutex); return m_Value; } private: int m_Value; std::mutex m_Mutex; }; int GetValue(const MyClass4& c) { return c.GetValue(); }
unique_ptr
Instead of raw pointer, using unique_ptr
works as well.
class MyClass5 { public: MyClass5(int value) : m_Value(value), m_pMutex(std::make_unique()) {} int GetValue() const { std::lock_guard<std::mutex> lock(*m_pMutex); return m_Value; } private: int m_Value; std::unique_ptr<std::mutex> m_pMutex; }; int GetValue(const MyClass5& c) { return c.GetValue(); }
The example code is hosted at Github.
Leave a Reply