2

I have two std::atomic variables, like this:

std::atomic<bool> b1;
std::atomic<bool> b2;

At some point in the code I need to swap them. This runs before the threads are created, so I know there is only the main thread and no-one else is trying to read/write to those vars. But:

std::swap(b1, b2);

This results in:

[...] MSVC\14.24.28314\include\utility(61,1): error C2280: 'std::atomic<bool>::atomic(const std::atomic<bool> &)': attempting to reference a deleted function
[...] MSVC\14.24.28314\include\atomic(1480): message : see declaration of 'std::atomic<bool>::atomic'
[...] MSVC\14.24.28314\include\atomic(1480,5): message : 'std::atomic<bool>::atomic(const std::atomic<bool> &)': function was explicitly deleted

I'm not sure why the copy constructor is deleted. So the solution I used was to use the old-style swap with a 3rd variable:

const bool tmp = b1;
b1 = b2.load();
b2 = tmp;

But now I'm curious: Why is std::atomic's copy constructor deleted?

(Actual code is more complicated than two single std::atomic<bool> variables, but I tried to distill it down to a simple case for this question.)

Stéphane
  • 19,459
  • 24
  • 95
  • 136
  • 1
    I like the question - but "_At some point in the code I need to swap them_" seems constructed. – Ted Lyngmo Mar 06 '20 at 00:55
  • 1
    There are large structures being swapped, lots of threads, mutexes, typical things in a complicated system. It isn't as simple as just 2 bools. But I simplified it down to the essentials for the purpose of the question. – Stéphane Mar 06 '20 at 00:58
  • Sounds like you are swapping the wrong things. – Ted Lyngmo Mar 06 '20 at 00:58
  • 5
    The title says "how to swap", but the body says "Why is std::atomic's copy constructor deleted?". I am closing based on the latter, since the question seems to understand and correctly piece together the reason why the swap didn't compile. – GManNickG Mar 06 '20 at 00:59
  • atomic types are not guaranteed to be lock-free. the atomic type might need to include an embedded mutex while mutexes are not copyable. – con ko Mar 06 '20 at 01:01
  • I suddenly realized what i have learnt was also from SO after I found your question closed... – con ko Mar 06 '20 at 01:03
  • 1
    b1 = b2.exchange( b1 ); – engf-010 Mar 06 '20 at 01:08
  • 1
    @engf-010: That would seq_cst read `b1`, do an atomic exchange (RMW) of that temporary with `b2`, and then afterwards seq_cst store the temporary result into `b1`. Does the OP *want* a seq-cst RMW of `b2` as opposed to `b1`? No indication that they do. Although if they're actually using `atomic` of a large struct, taking the lock once for an exchange is cheaper than separate load and store. (Opposite is true on most systems for lock-free atomics, especially if you use only acq/rel) – Peter Cordes Mar 06 '20 at 01:48
  • 1
    @engf-010: Note: That's still non-atomic; modifications can interleave between the read from `b1` and the `exchange`, and between the `exchange` and the write to `b1`. It's fine if no threads are involved, but you don't even need atomic types in that case. – ShadowRanger Mar 06 '20 at 01:49
  • 1
    If you want to be able to *atomically* swap two large structs, you should use your own locking instead of letting `std::atomic` do it for you behind the scenes for any `T` larger than 2 pointers on most systems. ([Where is the lock for a std::atomic?](https://stackoverflow.com/q/50298358)). Then you can acquire both locks at once. (Beware of deadlocks, though; one trick is to make sure everything that ever takes 2 locks takes them in order of increasing address.) Generally `atomic` is not great for efficiency if you expect it to be non-lock-free. Every single access locks/unlocks. – Peter Cordes Mar 06 '20 at 01:54
  • @ShadowRanger & Peter Cordes: I did realize it's not a full atomic operation ,but it was 1 line replacement for the code from OP and does the swap with the values at the time of the call. – engf-010 Mar 06 '20 at 02:03
  • 3
    @engf-010: Source-code size is one of the least important consideration for atomics. If you're using them at all instead of simple locking, presumably you care about performance, and the exact semantics of which operations you're doing. So ya, that's one way to do it that's worth considering, but it's not obviously better. (Unless you're using seq_cst anyway, then for most ISA an RMW isn't much more expensive than a pure store, and for lock-free objects most x86 compilers will actually use an atomic `xchg` as a store + full-barrier for a pure store, discarding the load part.) – Peter Cordes Mar 06 '20 at 02:47

1 Answers1

4

You realize what you did isn't atomic, right? It has all sorts of possible data races from the moment b1 is loaded, until the moment b2 is set.

Presumably the reason swap is forbidden is that it's not possible to perform an atomic operation that modifies two unrelated memory addresses (at least, not portably enough to support it as a language standard), so it refuses to lie to you by silently using non-atomic swapping.

Conveniently, there are other reasons to prohibit copy construction, so the default implementation of std::swap is blocked as a side-effect of prohibiting copy construction, so all they had to do was refuse to provide a lying swap implementation themselves.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • 1
    It's an answer so I suggest "_What you did isn't atomic_" instead of a question. – Ted Lyngmo Mar 06 '20 at 01:00
  • There are no possible data races since I specifically stated in the question that this happens when there are no other threads running, nothing else reading and nothing else writing to those variables. – Stéphane Mar 06 '20 at 01:03
  • 3
    @Stéphane: So your question is "Why can't I do a thing that would be dangerous to allow in all the cases the type is useful in, even when I'm careful to only do it when it's safe, and the type is wholly pointless?" Sorta answers itself; supporting an atomic scenario that is only safe when you don't need atomics, and dangerous when you do, would be a really poor design decision on the standards committee's part. – ShadowRanger Mar 06 '20 at 01:08
  • 2
    No, that wasn't my question at all. – Stéphane Mar 06 '20 at 01:09