38

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
1

4 Answers 4

71

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.

Sign up to request clarification or add additional context in comments.

1 Comment

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.)
34

How about the definition

std::atomic<int> order::c{0}

Comments

6

Also you can use atomic_init:

std::atomic<int> data;
std::atomic_init(&data, 0);

4 Comments

this would not be initialized during compilation?
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?
@anicicn 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 stackoverflow.com/questions/8102125/…
@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).
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

1 Comment

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.