I know, it has been made quite clear in a couple of questions/answers before, that volatile
is related to the visible state of the c++ memory model and not to multithreading.
On the other hand, this article by Alexandrescu uses the volatile
keyword not as a runtime feature but rather as a compile time check to force the compiler into failing to accept code that could be not thread safe. In the article the keyword is used more like a required_thread_safety
tag than the actual intended use of volatile
.
Is this (ab)use of volatile
appropriate? What possible gotchas may be hidden in the approach?
The first thing that comes to mind is added confusion: volatile
is not related to thread safety, but by lack of a better tool I could accept it.
Basic simplification of the article:
If you declare a variable volatile
, only volatile
member methods can be called on it, so the compiler will block calling code to other methods. Declaring an std::vector
instance as volatile
will block all uses of the class. Adding a wrapper in the shape of a locking pointer that performs a const_cast
to release the volatile
requirement, any access through the locking pointer will be allowed.
Stealing from the article:
template <typename T>
class LockingPtr {
public:
// Constructors/destructors
LockingPtr(volatile T& obj, Mutex& mtx)
: pObj_(const_cast<T*>(&obj)), pMtx_(&mtx)
{ mtx.Lock(); }
~LockingPtr() { pMtx_->Unlock(); }
// Pointer behavior
T& operator*() { return *pObj_; }
T* operator->() { return pObj_; }
private:
T* pObj_;
Mutex* pMtx_;
LockingPtr(const LockingPtr&);
LockingPtr& operator=(const LockingPtr&);
};
class SyncBuf {
public:
void Thread1() {
LockingPtr<BufT> lpBuf(buffer_, mtx_);
BufT::iterator i = lpBuf->begin();
for (; i != lpBuf->end(); ++i) {
// ... use *i ...
}
}
void Thread2();
private:
typedef vector<char> BufT;
volatile BufT buffer_;
Mutex mtx_; // controls access to buffer_
};
NOTE
After the first couple of answers have appeared I think I must clarify, as I might not have used the most appropriate words.
The use of volatile
is not because of what it provides at runtime but because of what it means at compile time. That is, the same trick could be pulled with the const
keyword if it was as rarely used in user defined types is as volatile
is. That is, there is a keyword (that happens to be spelled volatile) that allows me to block member function calls, and Alexandrescu is using it to trick the compiler into failing to compile thread-unsafe code.
I see it as many metaprogramming tricks that are there not because of what they do at compile time, but rather for what it forces the compiler to do for you.