How to Declare a Vector of Atomic in C++

How to declare a vector of atomic in C++

As described in this closely related question that was mentioned in the comments, std::atomic<T> isn't copy-constructible, nor copy-assignable.

Object types that don't have these properties cannot be used as elements of std::vector.

However, it should be possible to create a wrapper around the std::atomic<T> element that is copy-constructible and copy-assignable. It will have to use the load() and store() member functions of std::atomic<T> to provide construction and assignment (this is the idea described by the accepted answer to the question mentioned above):

#include <atomic>
#include <vector>

template <typename T>
struct atomwrapper
{
std::atomic<T> _a;

atomwrapper()
:_a()
{}

atomwrapper(const std::atomic<T> &a)
:_a(a.load())
{}

atomwrapper(const atomwrapper &other)
:_a(other._a.load())
{}

atomwrapper &operator=(const atomwrapper &other)
{
_a.store(other._a.load());
}
};

int main(void)
{
std::vector<atomwrapper<int>> v_a;
std::atomic<int> a_i(1);
v_a.push_back(a_i);
return 0;
}

EDIT: As pointed out correctly by Bo Persson, the copy operation performed by the wrapper is not atomic. It enables you to copy atomic objects, but the copy itself isn't atomic. This means any concurrent access to the atomics must not make use of the copy operation. This implies that operations on the vector itself (e.g. adding or removing elements) must not be performed concurrently.

Example: If, say, one thread modifies the value stored in one of the atomics while another thread adds new elements to the vector, a vector reallocation may occur and the object the first thread modifies may be copied from one place in the vector to another. In that case there would be a data race between the element access performed by the first thread and the copy operation triggered by the second.

Is it possible to create an atomic vector or array in C++?

In practice, at the CPU level, there are instructions which can atomically update an int, and a good compiler will use these for std::atomic<int>. In contrast, there are are no instructions which can atomically update a vector of ints (for any architecture I am aware of), so there has got to be a mutex of some sort somewhere. You might as well let it be your mutex.


For future readers who haven't yet written code with the mutex:

You can't create a std::atomic of int[10], because that leads to a function which returns an array - and you can't have those. What you can do, is have a std::atomic<std::array<int,10>>

int main()
{
std::atomic<std::array<int,10>> myArray;
}

Note that the compiler/library will create a mutex under the hood to make this atomic. Note further that this doesn't do what you want. It allows you to set the value of the whole array atomically.

It doesn't allow you to read the whole array, update one element, and write the whole array back atomically.

The reads and the writes will be individually atomic, but another thread can get in between the read and the write.

You need the mutex!

std::vector of class containing atomic

std::atomic is neither copyable nor movable, by design. Operations on std::vector which cause it to reallocate require its elements to be at least movable. So, you have the following options:

  • Stop storing std::atomic in the element class. Perhaps a std::unique_ptr<std::atomic> could be used instead.
  • Stop storing the element class directly in the vector, store std::unique_ptr<ElementClass> instead (as suggested by @Richard Critten in comments).
  • Write a copy or move constructor and assignment operator for your class, which will somehow work around the non-movability of std::atomic.
  • Give your class dummy copy/move operations to satisfy the compiler. Then, pre-allocate space in vector using reserve, and then only use functions which append elements (up to the preallocated size), access them, or delete from the end; no in-the-middle insertions or deletions. This way, the dummy operations will never actually be called.

    Given the fragility of this approach, I would suggest two precautions:

    1. Make the dummies throwing, so that you catch any violations of the "no resizing" requirement ASAP.
    2. Do not use std::vector directly, but wrap it in your own NonResizableVector<T> with a suitably restricted interface, and document it heavily.

Which one of these you should (or even can) use depends on what your class actually does.

Initialisation of vector of atomics

You are correct to be worried. According to standard the atomics has the default constructor called, however they have not been initialized as such. This is because the default constructor doesn't initialize the atomic:

The default-initialized std::atomic<T> does not contain a T object,
and its only valid uses are destruction and initialization by
std::atomic_init

This is somewhat in violation of the normal language rules, and some implementations initialize anyway (as you have noted).

That being said, I would recommend taking the extra step to make 100% sure they are initialized correctly according to standard - after all you are dealing with concurrency where bugs can be extremely hard to track down.

There are many ways to dodge the issue, including using wrapper:

struct int_atomic {
std::atomic<int> atomic_{0};//use 'initializing' constructor
};

Add (predefined) atomic values to vector in C++

I've found a SOLUTION to define atomic vector with values I want to use. It has solved the problem

// class_a_example_header.h

class class_a_example {
protected:
std::vector<std::atomic<uint32_t>> m_accessed;
// some other structures

public:
// some methods
inline class_a_example(/*params*/, size_t c_count, /*params*/) : m_vars(c_count, 0), m_accessed(c_count)
{
for (auto it = m_accessed.begin(); it != m_accessed.end(); it++)
std::atomic_init(&*it, 0u) //instead of 0u specify value you want to be in atomic
}
};

After this actions atomic vector will be created successfully & defined with values you want.

How to assign a vector of atomic types?

std::atomic is neither copyable or move constructible, so you might do instead:

std::vector<std::atomic<bool>> myvector(8);
for (auto& b : myvector) { std::atomic_init(&b, false); }

Resize a vector of atomic?

You can't...

A std::atomic<T> is neither copy/move-constructible, nor can you assign one std::atomic<T> to another; this means that it doesn't have the requirements to use std::vector<...>::resize(size_type).

23.3.6.2 vector constructors, copy, and assignment [vector.const]

void resize (size_type sz);

Requires: T shall be CopyInsertable into *this.

Note: std::vector::resize (size_type sz, T const& init) isn't applicable either since that requires T to also be MoveInsertable.


Proposed resolution

You will need to use some other container-type which doesn't require already constructed data to be moved, copied, or copy/move assigned, upon modifying the elements already stored inside.

You could also define a wrapper around your std::atomic that fakes copy/moves/assigns, but actually just shallow read/writes the value of the underlying atomic.

efficent way of casting or converting vector of atomic int to vector of int

In C++, atomic operations are a property of the object itself, not of how you access it. As far as the object model is concerned, atomic<int> has no real relationship to int save the fact that you can convert one to another. You cannot transform an atomic<int> into an int; you can only create a new object with the same value.



Related Topics



Leave a reply



Submit