Today I came across something simple I though might be interesting to others.
bool Client_Globals::IsInitialReplicationComplete()
{
m_mutex.Enter();
const bool isComplete = m_isInitialReplicationSent && m_galaxyUpdates.empty();
m_mutex.Leave();
return isComplete;
}
I was reviewing some code changes for a check-in today, and I thought to myself, this function should really be const because it doesn’t modify anything in Client_Globals. I proceeded to add const, and was immediately reminded that Mutex::Enter/Leave was not marked const. I went and looked at my implementation of Mutex::Enter, and was reminded of course that I was modifying some things.
class Mutex
{
public:
Mutex();
~Mutex();
void Enter() const;
void Leave() const;
bool IsInMutex() const;
private:
friend class Thread;
#if defined(_WIN32)
CRITICAL_SECTION m_cs;
DWORD m_currentThreadID;
#elif defined(__APPLE__) || defined(__linux__)
pthread_mutex_t m_mutex;
pthread_t m_currentThread;
#endif
s32 m_currentEntryCount;
};
void Mutex::Enter()
{
EnterCriticalSection(&m_cs);
if (0 == m_currentEntryCount)
{
m_currentThreadID = GetCurrentThreadId();
}
++m_currentEntryCount;
assert(m_currentThreadID == GetCurrentThreadId());
}
After internally debating the pros & cons of actually making Mutex::Enter const despite it modifying members, I concluded the benefit outweighed the cost in this case, and I proceeded to make Mutex::Enter const using a const_cast<>.
void Mutex::Enter() const
{
Mutex* pThis = const_cast<Mutex*>(this);
EnterCriticalSection(&(pThis->m_cs));
if (0 == pThis->m_currentEntryCount)
{
pThis->m_currentThreadID = GetCurrentThreadId();
}
++(pThis->m_currentEntryCount);
assert(pThis->m_currentThreadID == GetCurrentThreadId());
}
As I was making the change, I started wondering, hey this is 2024, I wonder if C++ has added a better way to do this. After some searching, I discovered in fact they did add something to help with this case in C++11 … the new keyword “mutable”. The “mutable” keyword allows you to mark a member variable as modifiable from within a const function. I modified my Mutex class as follows:
mutable CRITICAL_SECTION m_cs;
mutable DWORD m_currentThreadID;
mutable s32 m_currentEntryCount;
Once the members in Mutex were declared mutable, I was able to eliminate the const_cast<> trick in Mutex::Enter, which made the method simpler:
void Mutex::Enter() const
{
EnterCriticalSection(&m_cs);
if (0 == m_currentEntryCount)
{
m_currentThreadID = GetCurrentThreadId();
}
++m_currentEntryCount;
assert(m_currentThreadID == GetCurrentThreadId());
}
Of course, this new keyword can be used in dangerous ways. I probably wouldn’t use it to work around every const correctness problem, but it seemed like a fantastic compromise in this specific case.