J. Doe
J. Doe

Reputation: 228

Vector(array) of atomic variables

I understand that atomic has a copy constructor that's deleted, but what can I do to make this code work? How can I possibly define a copy constructor inside vector for atomic?

#include <atomic>
#include <vector>

int main() {
    std::vector<std::atomic<int>> examp;
    examp.resize(64);
}

Upvotes: 2

Views: 5583

Answers (4)

Quanbing Luo
Quanbing Luo

Reputation: 41

I have tried the following code in VS 2022 and it works:

#include <atomic>
#include <vector>

int main() {
    std::vector<std::atomic<int>> examp (64);    

}

However, as the std::atomic type is not copyable or movable, some member functions (such as resize()) could not be implemented. For a vector of atomic variables, many member functions impose stricter requirements.

See https://en.cppreference.com/w/cpp/container/vector

Upvotes: 1

Peter Cordes
Peter Cordes

Reputation: 364278

If you just need a runtime-variable size but don't need to resize the object after that, you can use std::vector< atomic<int> > elems(size) as @Quanbing Luo points out.

Member functions like .pop_back and .push_back won't compile, even if you've arranged things so it definitely won't exceed its .capacity() and have to grow. But other than that it should work fine.


In C++20, you can use std::atomic_ref to do atomic operations on a plain int object, so you can use std::vector<int> and

  static_assert(std::atomic_ref<int>::required_alignment == alignof(int), "vector elements need to be sufficiently aligned for atomic_ref");
  std::vector<int> examp(size);  // and potentially do whatever non-atomic init before sharing

  examp.resize(size2);

  std::atomic_ref<int> examp3(examp[3]);
  examp3.store(1, std::memory_order_release);

Perhaps use a typedef or something instead of bare int everywhere. And maybe even helper functions for some atomic ops so you don't have to manually construct an atomic_ref object in a separate statement. (Constructing one is free; it'll optimize away as long as you don't keep the atomic_ref object around. It only exists as an API to wrap stuff like GNU C __atomic_load_n(int *, int memorder), which you could use manually if you don't want to use C++20 features.)


Nothing can make it thread-safe to grow or shrink a vector while other threads are accessing elements of it, so there's no upside to the extra level of indirection in another answer's proposed std::vector<std::unique_ptr<std::atomic<int>>> examp;. The std::vector<unique_ptr> object itself (typically 3 pointers, .data(), .end() and end-of-allocation) is not atomic, and anything that reallocates the array while another thread is accessing it is a problem.

Writing inefficient code just to keep the compiler happy is kind of at odds with the purpose of using lock-free atomics instead of locking (performance at the expense of simplicity).

Upvotes: 0

Luis Guzman
Luis Guzman

Reputation: 1026

You can't have a vector of std::atomic<int> because it is not copyable or movable, but you can have a vector of unique_ptrs to atomic<int>. If you really need a run-time variable-size vector of atomics, this may be a viable alternative. Here is an example:

#include <iostream>
#include <atomic>
#include <vector>
#include <memory>

using namespace std;

int main() {
    std::vector<std::unique_ptr<std::atomic<int>>> examp;
    examp.resize(64);   // 64 default unique_ptrs; they point to nothing

    // init the vector with unique_ptrs that actually point to atomics
    for (auto& p : examp) {
        p = std::make_unique<std::atomic<int>>(0);   // init atomic ints to 0
    }

    // use it
    *examp[3] = 5;

    for (auto& p : examp) {
        cout << *p << ' ';
    }
    cout << '\n';
}

Upvotes: 6

user9143630
user9143630

Reputation: 183

std::atomic is not copyable or movable. As you noted, the copy constructor is deleted but no move constructor is generated. See http://en.cppreference.com/w/cpp/language/move_constructor:

If no user-defined move constructors are provided for a class type (struct, class, or union), and all of the following is true:

  • there are no user-declared copy constructors;

User-declared means "not added by the compiler" (i.e by default). Even though it is a library class, the constructor is user-declared.

The resize function for a vector requires that the type either be move-insertable or copy-insertable depending on the overload. See http://en.cppreference.com/w/cpp/container/vector/resize:

If the current size is less than count,

1) additional default-inserted elements are appended

2) additional copies of value are appended

What you're doing simply won't work.

Upvotes: 3

Related Questions