7

The following excerpt from the current draft shows what I mean:

namespace std {
    typedef struct atomic_bool {
        bool is_lock_free() const volatile;
        bool is_lock_free() const;
        void store(bool, memory_order = memory_order_seq_cst) volatile;
        void store(bool, memory_order = memory_order_seq_cst);
        bool load(memory_order = memory_order_seq_cst) const volatile;
        bool load(memory_order = memory_order_seq_cst) const;
        operator bool() const volatile;
        operator bool() const;
        bool exchange(bool, memory_order = memory_order_seq_cst) volatile;
        bool exchange(bool, memory_order = memory_order_seq_cst);
        bool compare_exchange_weak(bool&, bool, memory_order, memory_order) volatile;
        bool compare_exchange_weak(bool&, bool, memory_order, memory_order);
        bool compare_exchange_strong(bool&, bool, memory_order, memory_order) volatile;
        bool compare_exchange_strong(bool&, bool, memory_order, memory_order);
        bool compare_exchange_weak(bool&, bool, memory_order = memory_order_seq_cst) volatile;
        bool compare_exchange_weak(bool&, bool, memory_order = memory_order_seq_cst);
        bool compare_exchange_strong(bool&, bool, memory_order = memory_order_seq_cst) volatile;
        bool compare_exchange_strong(bool&, bool, memory_order = memory_order_seq_cst);
        atomic_bool() = default;
        constexpr atomic_bool(bool);
        atomic_bool(const atomic_bool&) = delete;
        atomic_bool& operator=(const atomic_bool&) = delete;
        atomic_bool& operator=(const atomic_bool&) volatile = delete;
        bool operator=(bool) volatile;
    } atomic_bool;
}

Volatile is transitive. Thus, you cannot call a non-volatile member function from a volatile object. On the other hand, calling a volatile member function from a non-volatile object is allowed.

So, is there any implementation difference between the volatile and non-volatile member functions in the atomic classes? In other words, is there any need for the non-volatile overload?

0xbadf00d
  • 17,405
  • 15
  • 67
  • 107
  • A better question is why there needs to be `volatile` overloads in the first place. – GManNickG Feb 02 '11 at 09:04
  • 1
    @GMan: because otherwise the functions couldn't be called on volatile data. ;) – jalf Feb 02 '11 at 09:08
  • 3
    @jalf: Ha, yes, but since the operations the type itself makes are atomic (and hence observable), why would we make a `volatile atomic<>`? I think I'm missing something major. – GManNickG Feb 02 '11 at 09:11
  • 1
    @GMan: but why shouldn't it be allowed? Suppose you have an `atomic<>` as a member of another struct, and a `volatile` instance is created? Then all its members will implicitly be `volatile` as well, and without the `volatile` overloads, your shiny new `atomic` class would be useless. :) – jalf Feb 02 '11 at 13:53
  • Oh, and digging a bit further, 29.6/3 has a note on the subject: "Many operations are volatile-qualified. The "volatile as device register" semantics have not changed in the standard. This qualification means that volatility is preserved when applying these operations to volatile objects." – jalf Feb 02 '11 at 13:56
  • @jalf: It wouldn't be "allowed" because it would be redundant, extra code. Your same reasoning means `std::string` should have all its member functions `volatile` too, doesn't it? – GManNickG Feb 02 '11 at 17:44
  • @GMan: no, because a `string` isn't intended to be used in a volatile manner (it's not thread-safe, so it wouldn't be able to handle if the underlying memory was suddenly changed by a driver or similar). But it'd be a pretty poor atomic variable if it can't be treated as volatile. ;) Remember that the intent of `volatile` is to indicate that the object may be modified without the program's knowledge. That doesn't make sense for a string, but it is something an object *intended* to be atomic should be able to handle. – jalf Feb 02 '11 at 17:50
  • @jalf: But atomic is *always* assumed to be modifiable without the program's knowledge, it's inherently `volatile` in nature. Marking it `volatile` has no semantic difference, unlike other `volatile` variables. – GManNickG Feb 02 '11 at 21:07
  • 1
    @GMan: exactly. If the object *behaves* as if it's volatile, then it should also work if I add the `volatile` qualifier, shouldn't it? Would you also argue that "there'd be no point in marking members of a class `const` if the class is semantically constant anyway"? It makes no difference, *other than to allow the class to keep working in a context where the relevant qualifier is present* – jalf Feb 03 '11 at 17:21
  • 1
    @jalf: "If the object behaves as if it's volatile, then it should also work if I add the volatile qualifier, shouldn't it" Perhaps, but my question is why we need to even *support* that, if the addition of they keyword makes no difference anyway. Your `const` example is great for showing why we need `const` on a member function, but what if we had a type that was inherently const? (Like, say, `std::integral_constant`.) What good is it to make an instance of that `const`? – GManNickG Feb 03 '11 at 22:07
  • @GMan and jalf +1: Yes, that's a good point. – 0xbadf00d Feb 04 '11 at 16:58
  • @GMan: for example what I already said: it might be const simply because it is a member of an object you create as const. my point is that we have to accept that these qualifiers can always be added to an object, and doing so shouldn't break the type. – jalf Feb 05 '11 at 03:43
  • Or you might have a function template which performs some operation on an unknown `volatile` object. So it takes a parameter of type `volatile T`. Now you obviously want to be able to pass an `atomic` to that function, because it is supposed to work with objects that are volatile. And blam, your code fails to compile because your semantically volatile object is no longer usable when the `volatile` qualifier is added to it. That'd be absurd, just like a hypothetical `std::integral_constant` should be able to be passed to a function that expects a `const T` without breaking. – jalf Feb 05 '11 at 03:44
  • @jalf: I think I see where you're coming from somewhat, so I'll tentatively accept that we need the overloads. But I still think it's strange for someone to ever have a `volatile atomic<>`. – GManNickG Feb 09 '11 at 12:25

2 Answers2

4

I think that the volatile overloads exist for efficiency reasons. Volatile reads and writes are inherently more expensive than non-volatile reads and writes in C++0x, since the memory model puts some stringent requirements that prevent caching of values of volatile variables. If all the functions were only marked volatile, then the code couldn't necessarily make certain optimizations that would otherwise improve performance. Having the distinction allows the compiler to optimize non-volatile reads and writes when possible while degrading gracefully when volatile reads and writes are required.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • 1
    The volatile qualifier just prevents compiler optimizations. Furthermore, as far as I know, volatile qualifier applied to member functions only allows the call of this method from volatile objects and doesn't effect resulting code. – 0xbadf00d Feb 02 '11 at 05:34
  • 4
    @FrEEzE2046- In C++0x the definition of `volatile` is a lot more rigidly specified and does actually mean more than just "don't optimize." Also, the more precise meaning of the volatile modifier on a member function is that the `this` pointer is `volatile`, and so any accesses to member variables that occur in the function would implicitly become volatile – templatetypedef Feb 02 '11 at 05:38
  • Okay, that means any access to the object, when calling a volatile member function, wouldn't be optimized? (Volatile description is: "Access to volatile objects are evaluated strictly according to the rules of the abstract machine.") – 0xbadf00d Feb 02 '11 at 06:52
  • 1
    +1 Whether to add the non-volatile versions was one of the open questions in earlier drafts. It was added specifically due to requests from compiler implementors. See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2925.html#LWG1147. – stephan Mar 03 '11 at 17:28
-1

First, it sounds redundant to make a volatile std::atomic. Actually, I can imagine an useful situation. Assuming we have an fixed device (memory-)address we want to operate on. Due to the fact that the std::atomic_xxx classes as well as the std::atomic<> template class sizes should be same size as their corresponding built-in-types, you might want to handle both: Performing atomic operations with control over memory ordering and make sure that the access to our atomic-object is never optimized. Thus, we could declare something like:

std::atomic<long> volatile* vmem_first4 = reinterpret_cast<std::atomic<long> volatile*>(0xxB8000);
0xbadf00d
  • 17,405
  • 15
  • 67
  • 107
  • The standard explicitly says that `atomic` may not have the same size as the equivalent `T` – jalf Feb 03 '11 at 17:40
  • 1
    "The representation of the atomic address type need not have the same size as its corresponding regular type. It should have the same size whenever possible." So, volatile atomic seems to be useless to me. – 0xbadf00d Feb 03 '11 at 20:20