How to initialize a static std::atomic data member
Asked Answered
B

4

36

I would like to generate identifiers for a class named order in a thread-safe manner. The code below does not compile. I know that the atomic types do not have copy constructors, and I assume that explains why this code does not work.

Does anybody know a way to actually get this code to work? Is there an alternative approach?

#include <atomic>
#include <iostream>

class order {
public: 
    order() { id = c.fetch_add(1); }
    int id;
private:
    static std::atomic<int> c;
};

std::atomic<int> order::c = std::atomic<int>(0);

int main() {
    order *o1 = new order();
    order *o2 = new order();
    std::cout << o1->id << std::endl; // Expect 0
    std::cout << o2->id << std::endl; // Expect 1
}

Compiling the above results in the following error:

order.cpp:45:51: error: use of deleted function 
        ‘std::atomic<int>::atomic(const std::atomic<int>&)’
In file included from order.cpp:3:0:
/usr/include/c++/4.7/atomic:594:7: error: declared here
Brittenybrittingham answered 8/12, 2013 at 11:50 Comment(1)
Related: Copy-initialization of atomic variables in C++11Kickshaw
A
69

I know that the atomic types do not have copy constructors, and I assume that explains why this code does not work.

Yes, the error says that quite clearly.

Does anybody know a way to actually get this code to work?

Instead of copy-initialising from a temporary, which requires an accessible copy constructor:

std::atomic<int> order::c = std::atomic<int>(0);

use direct-initialisation, which doesn't:

std::atomic<int> order::c(0);   // or {0} for a more C++11 experience

You should probably prefer that anyway, unless you enjoy reading unnecessarily verbose code.

Anopheles answered 8/12, 2013 at 11:53 Comment(1)
In C++17 and later (at least with GCC) you can even write std::atomic_int order::c = 0;. godbolt.org/z/TsGKzYG9e . Of course at global scope, this is static storage, so std::atomic<int> order::c; was already sufficient to get a 0. (C11 had an ATOMIC_VAR_INIT but even then it wasn't needed, removed in C17. An all-zero object-representation is a valid state that's ready for use, and static storage always does that.)Cletus
F
34

How about the definition

std::atomic<int> order::c{0}
Ferry answered 8/12, 2013 at 11:53 Comment(0)
A
6

Also you can use atomic_init:

std::atomic<int> data;
std::atomic_init(&data, 0);
Acerbate answered 1/6, 2017 at 10:15 Comment(4)
this would not be initialized during compilation?Schick
Isn't static variable thread safe from c++11? I see the ++11 tag up in the question. So why do we need atomics on the first place? Why can't we just use regular int?Taunton
@Taunton A bit late, but the initialization of the static variable is guaranteed to only run once and is thread safe. After that, it's essentially just a regular int, i.e. not thread safe. See #8102625Augustineaugustinian
@SergKryvonos: Correct, you can't put this at global scope. You could put the std::atomic_init(&data, 0); at the top of main, but that's ugly and inefficient compared to just std::atomic<int> data {}; (which also works in function scope). Or for static storage like in the question, even just std::atomic<int> data; works; static storage is zero-initialized by default, and this is sufficient for std::atomic or C _Atomic to work correctly (C17 required that and deprecated ATOMIC_VAR_INIT).Cletus
C
0

Since std::atomic_init has been deprecated in C++20, here is a reimplementation which does not raise deprecation warnings, if you for some reason want to keep doing this.

static inline void sys_atomic_init(sys_atomic_t *ref, uint32_t value)
{
#ifdef __cplusplus
    // atomic_init is deprecated in C++20
    // use the equivalent definition from https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0883r2.pdf
    atomic_store_explicit(ref, value, std::memory_order_relaxed);
#else
    atomic_init(ref, value);
#endif
}

from https://github.com/skupperproject/skupper-router/blob/cfc8b4c6892dafc5dd0c86682b4db87a488f2451/include/qpid/dispatch/atomic.h#L41

Camelopardalis answered 30/12, 2023 at 15:41 Comment(1)
Perhaps #if defined(__cplusplus) && (__cplusplus < 202002L) so your init is fully non-atomic in languages / versions that allow that without deprecation warnings. Might avoid an extra store for space that was already initialized with value, e.g. if that value is 0 and the surrounding bytes are also being zeroed. (Compilers don't optimize away atomic stores, so the relaxed store will run an asm instruction to do it.) Not a big deal for uint32_t where all mainstream platforms can do that lock-free so it's cheap.Cletus

© 2022 - 2024 — McMap. All rights reserved.