How to declare a vector of atomic in C++
Solution 1
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.
Solution 2
To first answer your headline question: you can declare a std::vector<std::atomic<...>>
easily, as you have done in your example.
Because of the lack of copy or move constructors for std::atomic<>
objects, however, your use of the vector
will be restricted as you found out with the compilation error on push_back()
. Basically you can't do anything that would invoke either constructor.
This means your vector's size must be fixed at construction, and you should manipulate elements using operator[]
or .at()
. For your example code, the following works1:
std::vector<std::atomic<int>> v_a(1);
std::atomic<int> a_i(1);
v_a[0] = a_i.load();
If the "fixed size at construction" limitation is too onerous, you can use std::deque
instead. This lets you emplace objects, growing the structure dynamically without requiring copy or move constructors, e.g.:
std::deque<std::atomic<int>> d;
d.emplace_back(1);
d.emplace_back(2);
d.pop_back();
There are still some limitations, however. For example, you can pop_back()
, but you cannot use the more general erase()
. The limitations make sense: an erase()
in the middle of the blocks of contiguous storage used by std::deque
in general requires the movement of elements, hence requires copy/move constructor or assignment operators to be present.
If you can't live with those limitations, you could create a wrapper class as suggested in other answers but be aware of the underlying implementation: it makes little sense to move a std::atomic<>
object once it is being used: it would break any threads concurrently accessing the objects. The only sane use of copy/move constructors is generally in the initial setup of collections of these objects before they are published to other threads.
1 Unless, perhaps, you use Intel's icc
compiler, which fails with an internal error while compiling this code.
Solution 3
Looks to me like atomic<T>
has no copy constructor. Nor a move constructor, as far as I can tell.
One work around might be to use vector<T>::emplace_back()
to construct the atomic in-place in the vector. Alas, I don't have a C++11 compiler on me right now, or I'd go and test it.
![steffen](https://i.stack.imgur.com/GKjUX.png?s=256&g=1)
steffen
I am a Python freak and data nerd. Also, I am experimenting with creating videos on youtube. About Python and data. ¯(°_o)/¯ https://www.youtube.com/channel/UCG9XNnq9LodijOBpIVy1ILg
Updated on July 09, 2022Comments
-
steffen almost 2 years
I am intending to declare a vector of atomic variables to be used as counters in a multithreaded programme. Here is what I tried:
#include <atomic> #include <vector> int main(void) { std::vector<std::atomic<int>> v_a; std::atomic<int> a_i(1); v_a.push_back(a_i); return 0; }
And this is the annoyingly verbose error message of gcc 4.6.3:
In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0, from /usr/include/c++/4.6/bits/allocator.h:48, from /usr/include/c++/4.6/vector:62, from test_atomic_vec.h:2, from test_atomic_vec.cc:1: /usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = std::atomic<int>, __gnu_cxx::new_allocator<_Tp>::pointer = std::atomic<int>*]’: /usr/include/c++/4.6/bits/stl_vector.h:830:6: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’ test_atomic_vec.cc:10:20: instantiated from here /usr/include/c++/4.6/ext/new_allocator.h:108:9: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’ /usr/include/c++/4.6/atomic:538:7: error: declared here In file included from /usr/include/c++/4.6/vector:70:0, from test_atomic_vec.h:2, from test_atomic_vec.cc:1: /usr/include/c++/4.6/bits/vector.tcc: In member function ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’: /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’ test_atomic_vec.cc:10:20: instantiated from here /usr/include/c++/4.6/bits/vector.tcc:319:4: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’ /usr/include/c++/4.6/atomic:538:7: error: declared here /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’ test_atomic_vec.cc:10:20: instantiated from here /usr/include/c++/4.6/bits/vector.tcc:319:4: error: use of deleted function ‘std::atomic<int>& std::atomic<int>::operator=(const std::atomic<int>&)’ /usr/include/c++/4.6/atomic:539:15: error: declared here In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0, from /usr/include/c++/4.6/bits/allocator.h:48, from /usr/include/c++/4.6/vector:62, from test_atomic_vec.h:2, from test_atomic_vec.cc:1: /usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, _Args&& ...) [with _Args = {std::atomic<int>}, _Tp = std::atomic<int>, __gnu_cxx::new_allocator<_Tp>::pointer = std::atomic<int>*]’: /usr/include/c++/4.6/bits/vector.tcc:306:4: instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’ /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’ test_atomic_vec.cc:10:20: instantiated from here /usr/include/c++/4.6/ext/new_allocator.h:114:4: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’ /usr/include/c++/4.6/atomic:538:7: error: declared here In file included from /usr/include/c++/4.6/vector:61:0, from test_atomic_vec.h:2, from test_atomic_vec.cc:1: /usr/include/c++/4.6/bits/stl_algobase.h: In static member function ‘static _BI2 std::__copy_move_backward<true, false, std::random_access_iterator_tag>::__copy_move_b(_BI1, _BI1, _BI2) [with _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’: /usr/include/c++/4.6/bits/stl_algobase.h:581:18: instantiated from ‘_BI2 std::__copy_move_backward_a(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’ /usr/include/c++/4.6/bits/stl_algobase.h:590:34: instantiated from ‘_BI2 std::__copy_move_backward_a2(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’ /usr/include/c++/4.6/bits/stl_algobase.h:661:15: instantiated from ‘_BI2 std::move_backward(_BI1, _BI1, _BI2) [with _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]’ /usr/include/c++/4.6/bits/vector.tcc:313:4: instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’ /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’ test_atomic_vec.cc:10:20: instantiated from here /usr/include/c++/4.6/bits/stl_algobase.h:546:6: error: use of deleted function ‘std::atomic<int>& std::atomic<int>::operator=(const std::atomic<int>&)’ /usr/include/c++/4.6/atomic:539:15: error: declared here In file included from /usr/include/c++/4.6/vector:63:0, from test_atomic_vec.h:2, from test_atomic_vec.cc:1: /usr/include/c++/4.6/bits/stl_construct.h: In function ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = std::atomic<int>, _Args = {std::atomic<int>}]’: /usr/include/c++/4.6/bits/stl_uninitialized.h:77:3: instantiated from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*, bool _TrivialValueTypes = false]’ /usr/include/c++/4.6/bits/stl_uninitialized.h:119:41: instantiated from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*]’ /usr/include/c++/4.6/bits/stl_uninitialized.h:259:63: instantiated from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*, _Tp = std::atomic<int>]’ /usr/include/c++/4.6/bits/stl_uninitialized.h:269:24: instantiated from ‘_ForwardIterator std::__uninitialized_move_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = std::atomic<int>*, _ForwardIterator = std::atomic<int>*, _Allocator = std::allocator<std::atomic<int> >]’ /usr/include/c++/4.6/bits/vector.tcc:343:8: instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]’ /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]’ test_atomic_vec.cc:10:20: instantiated from here /usr/include/c++/4.6/bits/stl_construct.h:76:7: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)’ /usr/include/c++/4.6/atomic:538:7: error: declared here
How do I solve this?
The error disappears when I comment out the line with
push_back()
.edit: I edited the post... For those of you who saw the first post, the error was embarrassingly that I used
gcc
instead ofg++
:\ -
steffen over 11 yearsThat seems to be the problem. putting
v_a.push_back(std::move(a_i));
doesn't solve it though. -
steffen over 11 yearsthat seems to do the job... Is there any downside to this solution? If not... why isn't this implemented in std::atomic in the first place?
-
Bo Persson over 11 yearsOne problem is that
_a.store(other._a.load());
doesn't look very atomic to me. Is this useful? -
jogojapan over 11 years@steffen The only downside I am aware of is that the implementation will have to take all necessary precautions to ensure all copies and assignments are performed atomically. This may involve memory fences and locks and therefore slow down the insertion of elements in the vector as well as reallocation and copy operations performed on the vector itself. But that is what it means to work with atomics. You can pass special memory model parameters as arguments to the
load
andstore
calls to improve things, but choosing the right memory model (other than the safe default one) is an art. -
RobotMan over 11 years@steffen You're right, atomic doesn't have a move constructor either. I have updated my answer with a work-around that is different to the accepted one.
-
jogojapan over 11 years@BoPersson You are basically saying a mere assignment would perform the same operation as
store
, correct? Yes, I think that's right. Usingstore()
explicitly helps emphasize that an atomic store operation happens there. -
jogojapan over 11 yearsI tried using
emplace_back(1)
instead ofpush_back(a_i)
, but GCC 7.2 rejects that, saying that the necessary unitialized-copy operation requires the copy constructor. I guess that is due to possible reallocations when a new element is inserted. Anyway, even if certain compilers accepted it, it would still be an incorrect use ofstd::vector
, at least formally, because you are not supposed to use a non-copy-assignable element type. -
Bo Persson over 11 yearsI see a problem with this not being one, but two, atomic operations. Just wonder if that is really useful and what the OP needs.
-
jogojapan over 11 years@BoPersson Ah. That's right. For the OP: Indeed this implementation enables concurrent access to the elements of the vector, but not the vector itself. 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.
-
steffen over 11 years@BoPersson,@jogojapan: Good point. For the application I am working on right now it is ok, because I can separate initialisation and usage. But it's something to keep in mind.
-
betabandido over 11 years@jogojapan I just had a look at the C++ standard, and containers requirements are set on a per-operation basis (see section 23.2). That means, that there is no such a thing as an incorrect use of
std::vector
based on the element type. Of course, depending on the element type that you use, you will not be able to perform some operations. But if you do not need those operations, then using that element type should be totally fine. -
jogojapan over 11 years@betabandido Yes, strictly speaking that's right. The requirements are defined per operation, because that is how templates work in C++ -- only functions actually used are instantiated. However, it seems pretty clear that the idea is to have certain requirements on a per-class basis, and to actually enforce these using the concept of "concepts" (which unfortunately didn't make it into C++11). If you use GCC with the
-D_GLIBCXX_CONCEPT_CHECKS
option, the compiler will perform a limited set of per-class checks, and that did include the copy-assignment check for vector the last time I checked. -
v.oddou about 7 years@jogojapan I hope nobody assumes that a collection of concurrent objects is a concurrent collection of objects.
-
Peter Cordes over 6 yearsYour constructors should take an
order = seq_cst
default arg, for use as theorder
for loads. I guess there's no way to getvector
use it. Or maybe you should default torelaxed
since this constructor is only intended for use while copying temporaries during construction or growth, and you can't resize / reserve / emplace_back on a vector that other threads have pointers into. Especially for the.store()
in the copy-assignment operator. seq-cst stores are expensive. (And you can only do this while you hold a lock). -
Peter Cordes over 6 yearsAlso make sure you don't do
a[i] = b;
and get one of these overloads. The OP's example is silly; copying from anotherstd::atomic<int> a_i
is really weird. -
Peter Cordes over 6 yearsWhat I mean is: you only need to support
push_back
/emplace_back(int)
(orconst T&
) notatomic_int
/const atomic<T> &
. You should still force the user to explicitly load the value from some other atomic object. (And of course only one thread can grow the vector because the control block isn't atomic. But if you've reserved enough capacity to guarantee no reallocation, one thread can append while other threads are loading and storing earlier elements of the vector.) -
BeeOnRope over 6 yearsThe core claim that Object types that don't have these properties cannot be used as elements of
std::vector
doesn't seem to be true, at least in C++11 and beyond. Objects without copy or move constructors can be used as long as you don't use operations on the vector that would require them, as described here. This doesn't mean you can't usepush_back
oremplace_back
, but this isn't as restrictive as it sounds: moving anstd::atomic<>
object in memory pretty much breaks any concurrent accessors, so it's not a realistic operation. -
Andy over 6 yearsComments are not for extended discussion; this conversation has been moved to chat.
-
Peter Cordes over 6 years
emplace_back
doesn't work because it might have to grow the vector, and that requires copying the existing elements, requiring the non-existent copy-constructor for theatomic<T>
elements. There's no "unsafe_emplace_back" that doesn't check capacity. You could define a wrapper foratomic
that had a copy constructor, but probably just don't. -
codetaku almost 6 yearsThis is probably the best answer among the answers, but it's worth noting that you can use std::atomic as the vector type as long as you know how many elements you're going to have in advance. That is, if you know how many you'll have at the time you call the initializer, you'll be fine, such as vector<atomic<float>> vars(number_of_elements); to construct [number_of_elements] atomic floats which you can then use as you wish. It won't work with resize or push_back from there, though.
-
thefern about 2 yearsthis line
v_a[0] = a_i;
should bev_a[0] = a_i.load();
tried doing an edit but says suggestions are full. -
BeeOnRope about 2 years@thefern - good catch, I've fixed it up.
-
Ben Voigt about 2 yearsNote to the modern reader: Use
make_shared
instead of the nakednew
operator found in the last code snippet.