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 m
C++03 §7.1.5.1p7:
If an attempt is made to refer to an object defined with a volatile-qualified type through the use of an lvalue with a non-volatile-qualified type, the program behaviour is undefined.
Because buffer_ in your example is defined as volatile, casting it away is undefined behavior. However, you can get around that with an adapter which defines the object as non-volatile, but adds volatility:
template
struct Lock;
template
struct Volatile {
Volatile() : _data () {}
Volatile(T const &data) : _data (data) {}
T volatile& operator*() { return _data; }
T const volatile& operator*() const { return _data; }
T volatile* operator->() { return &**this; }
T const volatile* operator->() const { return &**this; }
private:
T _data;
Mutex _mutex;
friend class Lock;
};
The friendship is needed to strictly control non-volatile access through an already locked object:
template
struct Lock {
Lock(Volatile &data) : _data (data) { _data._mutex.lock(); }
~Lock() { _data._mutex.unlock(); }
T& operator*() { return _data._data; }
T* operator->() { return &**this; }
private:
Volatile &_data;
};
Example:
struct Something {
void action() volatile; // Does action in a thread-safe way.
void action(); // May assume only one thread has access to the object.
int n;
};
Volatile data;
void example() {
data->action(); // Calls volatile action.
Lock locked (data);
locked->action(); // Calls non-volatile action.
}
There are two caveats. First, you can still access public data members (Something::n), but they will be qualified volatile; this will probably fail at various points. And second, Something doesn't know if it really has been defined as volatile and casting away that volatile (from "this" or from members) in methods will still be UB if it has been defined that way:
Something volatile v;
v.action(); // Compiles, but is UB if action casts away volatile internally.
The main goal is achieved: objects don't have to be aware that they are used this way, and the compiler will prevent calls to non-volatile methods (which is all methods for most types) unless you explicitly go through a lock.