I noticed that most member functions of std::atomic<T>
types are declared twice, once with the volatile
modifier and once without (example)). I checked the source code of the G++ standard library implementation and I found all of them to be exact duplicates, for example,
bool
load(memory_order __m = memory_order_seq_cst) const noexcept
{ return _M_base.load(__m); }
bool
load(memory_order __m = memory_order_seq_cst) const volatile noexcept
{ return _M_base.load(__m); }
I could not find any example where the volatile
variant behaves differently than the non-volatile
one, differs in return type or anything of that sort.
Why is that? I thought a volatile
member function could also be called in objects which are not volatile
. So declaring and defining std::atomic::load(...) const volatile noexcept
etc. should be enough.
Update:
Based on the comments my question basically boils down to: Can you provide an example where some calls using a non-volatile
instance (not necessarily of std::atomic
) would generate a different assembly in the two following cases,
every member function appears with the same body with and without volatile
,
only the volatile
variant exists?
This, assuming the compiler can do whatever optimization the standard allows it to (or simply highest optimization levels).
Atomic variable examples In order to use atomicity in your program, use the template argument std::atomic on the attributes. Note, that you can't make your whole class atomic, just it's attributes. }; You don't need to use volatile along with std::atomic .
Constant and volatile member functions (C++ only) A nonconstant member function can only be called for a nonconstant object. Similarly, a member function declared with the volatile qualifier can be called for volatile and nonvolatile objects. A nonvolatile member function can only be called for a nonvolatile object.
Probably it all stems from what volatile
is, for that see this answer. As the use-cases are quite slim compare to usual application development, that is why nobody usually cares. I will assume that you do not have any practical scenario where you wondering if you should apply those volatile overloadings. Then I will try to come up with an example where you may need those (do not judge it to be too real).
volatile std::sig_atomic_t status = ~SIGINT;
std::atomic<int> shareable(100);
void signal_handler(int signal)
{
status = signal;
}
// thread 1
auto old = std::signal(SIGINT, signal_handler);
std::raise(SIGINT);
int s = status;
shareable.store(10, std::memory_order_relaxed);
std::signal(SIGINT, old);
// thread 2
int i = shareable.load(std::memory_order_relaxed);
memory_order_relaxed
guarantees atomicity and modification order consistency, no side-effects. volatile
cannot be reordered with side-effects. Then here we are, in thread 2 you can get shareable
equal 10, but status is still not SIGINT
. However, if you set type qualifier to volatile
of shareable
that must be guaranteed. For that you will need the member methods be volatile
-qualified.
Why would you do something like this at all? One case, I might think of, is you have some old code that is using old volatile
-based stuff and you cannot modify it for one or another reason. Hard to imagine, but I guess that there might be a need to have some kind of guaranteed order between atomic
and volatile
inline assembly. The bottom line, IMHO, is that whenever it is possible you can use new atomic library instead of volatile
objects, in the case there are some volatile
objects, that you cannot rid of, and you want to use atomic
objects, then you might need volatile
qualifier for the atomic
objects to have proper ordering guarantees, for that you would need overloading.
UPDATE
But if all I wanted was to have atomic types usable as both volatile and non-volatile, why not just implement the former?
struct Foo {
int k;
};
template <typename T>
struct Atomic {
void store(T desired) volatile { t = desired; }
T t;
};
int main(int i, char** argv) {
//error: no viable overloaded '='
// void store(T desired) volatile { t = desired; }
Atomic<Foo>().store(Foo());
return 0;
}
The same would be with load
and other operations, because those are usually not trivial implementations that require copy operator and/or copy constructor (that also can volatile
or non-volatile
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With