Как объявить вектор атома в С++
Я намерен объявить вектор атомных переменных, который будет использоваться в качестве счетчиков в многопоточной программе. Вот что я пробовал:
#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;
}
И это досадно подробное сообщение об ошибке 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
Как я могу это решить?
Ошибка исчезает, когда я прокомментирую строку с помощью push_back()
.
edit: Я редактировал сообщение... Для тех из вас, кто видел первый пост, ошибка была неловкой, что я использовал gcc
вместо g++
:\
Ответы
Ответ 1
Как описано в этом тесно связанном вопросе, о котором упоминалось в комментариях, std::atomic<T>
не является конструктивным для копирования и не может назначаться для копирования.
Типы объектов, которые не имеют этих свойств, не могут использоваться как элементы std::vector
.
Тем не менее, должно быть возможно создать оболочку вокруг элемента std::atomic<T>
, который можно скопировать и скопировать. Он должен будет использовать функции-члены load()
и store()
std::atomic<T>
для обеспечения построения и назначения (это идея, описанная принятым ответом на упомянутый выше вопрос):
#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: Как правильно указал Бо Перссон, операция копирования, выполняемая оберткой, не является атомарной. Он позволяет копировать атомные объекты, но сама копия не является атомарной. Это означает, что любой одновременный доступ к атоматике не должен использовать операцию копирования. Это означает, что операции над самим вектором (например, добавление или удаление элементов) не должны выполняться одновременно.
Пример. Если, скажем, один поток изменяет значение, хранящееся в одной из атомов, в то время как другой поток добавляет новые элементы в вектор, может произойти перераспределение векторов, и объект, который изменяет первый поток, может быть скопирован из одного места в вектор к другому. В этом случае между доступом к элементу, выполняемым первым потоком, будет выполняться гонка данных и операция копирования, инициированная второй.
Ответ 2
Мне кажется, что atomic<T>
не имеет конструктора копирования. Насколько я могу судить, конструктор перемещения.
Одна работа может заключаться в использовании vector<T>::emplace_back()
для построения атома в месте в векторе. Увы, у меня сейчас нет компилятора С++ 11, или я бы пошел и протестировал его.
Ответ 3
Чтобы сначала ответить на ваш заголовок вопроса: вы можете легко объявить std::vector<std::atomic<...>>
, как это было в вашем примере.
Из-за отсутствия конструкторов копирования или перемещения объектов std::atomic<>
, однако, использование vector
будет ограничено, как вы узнали, с ошибкой компиляции на push_back()
. В принципе, вы не можете делать ничего, что вызывало бы любой конструктор.
Это означает, что ваш векторный размер должен быть зафиксирован при построении, и вы должны управлять элементами с помощью operator[]
или .at()
. Для вашего примера кода следующие работы 1:
std::vector<std::atomic<int>> v_a(1);
std::atomic<int> a_i(1);
v_a[0] = a_i;
Если ограничение "фиксированный размер при строительстве" слишком обременительно, вы можете использовать std::deque
. Это позволяет размещать объекты, динамически увеличивая структуру, не требуя копирования или перемещения конструкторов, например:
std::deque<std::atomic<int>> d;
d.emplace_back(1);
d.emplace_back(2);
d.pop_back();
Тем не менее, существуют некоторые ограничения. Например, вы можете pop_back()
, но вы не можете использовать более общий erase()
. Ограничения имеют смысл: a erase()
в середине блоков непрерывного хранилища, используемого std::deque
, в общем, требует перемещения элементов, поэтому требуется, чтобы присутствовали оператор копирования/перемещения или операторы присваивания.
Если вы не можете жить с этими ограничениями, вы можете создать класс-оболочку, как предлагается в других ответах, но знать о базовой реализации: нет смысла перемещать объект std::atomic<>
после его использования: it будет разбивать любые потоки, одновременно получающие доступ к объектам. Единственное разумное использование конструкторов copy/move обычно находится в начальной настройке коллекций этих объектов до их публикации в других потоках.
1 Если, возможно, вы не используете компилятор Intel icc
, который выходит из строя с внутренней ошибкой, в то время как компиляция этого кода.