Я намерен объявить вектор атомарных переменных для использования в качестве счетчиков в многопоточной программе. Вот что я попробовал:
#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()
,
редактировать: Я редактировал пост … Для тех из вас, кто видел первый пост, ошибка была смущающей, что я использовал gcc
вместо g++
: \
Как описано в этом тесно связанный вопрос это было упомянуто в комментариях, 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;
}
РЕДАКТИРОВАТЬ: Как правильно указал Бо Перссон, операция копирования, выполняемая оболочкой не является атомарным. Это позволяет вам копировать атомарные объекты, но сама копия не является атомарной. Это означает, что любой параллельный доступ к элементам не должен использовать операцию копирования. Это подразумевает, что операции над самим вектором (например, добавление или удаление элементов) не должны выполняться одновременно.
Пример: если, скажем, один поток изменяет значение, хранящееся в одной из атомик, в то время как другой поток добавляет новые элементы в вектор, может произойти перераспределение вектора, и объект, который модифицирует первый поток, может быть скопирован из одного места в векторе в другое , В этом случае может быть гонка данных между доступом к элементу, выполняемым первым потоком, и операцией копирования, запускаемой вторым.
Выглядит как atomic<T>
не имеет конструктора копирования. Насколько я могу судить, ни на шаг конструктора.
Один обходной путь может быть использовать vector<T>::emplace_back()
построить атом на месте в векторе. Увы, сейчас у меня нет компилятора C ++ 11, или я бы пошел и протестировал его.
Чтобы сначала ответить на ваш главный вопрос: вы можете объявлять 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()
, Ограничения имеют смысл: erase()
в середине блоков непрерывного хранения, используемых std::deque
в общем случае требует перемещения элементов, поэтому требует наличия конструктора копирования / перемещения или операторов присваивания.
Если вы не можете жить с этими ограничениями, вы можете создать класс-оболочку, как предложено в других ответах, но помните о базовой реализации: он делает мало смысла переместить std::atomic<>
объект, если он используется: он прерывает любые потоки, одновременно обращающиеся к объектам. Единственное разумное использование конструкторов копирования / перемещения, как правило, заключается в начальной настройке коллекций этих объектов перед их публикацией в других потоках.
1 Если, возможно, вы не используете Intel icc
компилятор, который не удается из-за внутренней ошибки во время компиляции этого кода.
Как правильно заметили другие, причиной ошибки компилятора является то, что std :: atomic явно запрещает конструктор копирования.
У меня был случай, когда я хотел удобство карты STL (в частности, я использовал карту карт, чтобы получить разреженную двумерную матрицу атомов, чтобы я мог сделать что-то вроде int val = my_map[10][5]
). В моем случае в программе будет только один экземпляр этой карты, поэтому он не будет скопирован, и даже лучше, я могу инициализировать все это с помощью фигурной инициализации. Поэтому было очень прискорбно, что, хотя мой код никогда не будет пытаться копировать отдельные элементы или саму карту, мне было запрещено использовать контейнер STL.
Обходной путь, с которым я в конечном счете пошел, состоит в том, чтобы сохранить std :: atomic внутри std :: shared_ptr. Это имеет плюсы, но, возможно, минус:
Плюсы:
За или против (желательность этого аспекта зависит от вариантов использования программ):
— есть только один общий атомарный для данного элемента. Таким образом, копирование shared_ptr или контейнера STL все равно даст один общий атомарный элемент. Другими словами, если вы скопируете контейнер STL и измените один из атомарных элементов, соответствующий атомарный элемент другого контейнера также будет отражать новое значение.
В моем случае Pro / Con характеристика была спорно из-за мое использование.
Вот краткий синтаксис для инициализации std :: vector с помощью этого метода:
#include <atomic>
#include <memory>
#include <vector>
std::vector<std::shared_ptr<std::atomic<int> > > vecAtomicInts
{
std::shared_ptr<std::atomic<int> >(new std::atomic<int>(1) ),
std::shared_ptr<std::atomic<int> >(new std::atomic<int>(2) ),
};
// push_back, emplace, etc all supported
vecAtomicInts.push_back(std::shared_ptr<std::atomic<int> >(new std::atomic<int>(3) ) );
// operate atomically on element
vecAtomicInts[1]->exchange(4);
// access random element
int i = *(vecAtomicInts[1]);