Cómo declarar un vector de atómica en C ++

Tengo la intención de declarar un vector de variables atómicas para usar como contadores en un progtwig multiproceso. Esto es lo que intenté:

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

Y este es el mensaje de error molesto y detallado de 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::construct(__gnu_cxx::new_allocator::pointer, const _Tp&) [with _Tp = std::atomic, __gnu_cxx::new_allocator::pointer = std::atomic*]': /usr/include/c++/4.6/bits/stl_vector.h:830:6: instantiated from 'void std::vector::push_back(const value_type&) [with _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::value_type = std::atomic]' 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::atomic(const std::atomic&)' /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::_M_insert_aux(std::vector::iterator, _Args&& ...) [with _Args = {const std::atomic&}, _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::iterator = __gnu_cxx::__normal_iterator<std::atomic*, std::vector<std::atomic > >, typename std::_Vector_base::_Tp_alloc_type::pointer = std::atomic*]': /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from 'void std::vector::push_back(const value_type&) [with _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::value_type = std::atomic]' 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::atomic(const std::atomic&)' /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::push_back(const value_type&) [with _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::value_type = std::atomic]' 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& std::atomic::operator=(const std::atomic&)' /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::construct(__gnu_cxx::new_allocator::pointer, _Args&& ...) [with _Args = {std::atomic}, _Tp = std::atomic, __gnu_cxx::new_allocator::pointer = std::atomic*]': /usr/include/c++/4.6/bits/vector.tcc:306:4: instantiated from 'void std::vector::_M_insert_aux(std::vector::iterator, _Args&& ...) [with _Args = {const std::atomic&}, _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::iterator = __gnu_cxx::__normal_iterator<std::atomic*, std::vector<std::atomic > >, typename std::_Vector_base::_Tp_alloc_type::pointer = std::atomic*]' /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from 'void std::vector::push_back(const value_type&) [with _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::value_type = std::atomic]' 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::atomic(const std::atomic&)' /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::__copy_move_b(_BI1, _BI1, _BI2) [with _BI1 = std::atomic*, _BI2 = std::atomic*]': /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*, _BI2 = std::atomic*]' /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*, _BI2 = std::atomic*]' /usr/include/c++/4.6/bits/stl_algobase.h:661:15: instantiated from '_BI2 std::move_backward(_BI1, _BI1, _BI2) [with _BI1 = std::atomic*, _BI2 = std::atomic*]' /usr/include/c++/4.6/bits/vector.tcc:313:4: instantiated from 'void std::vector::_M_insert_aux(std::vector::iterator, _Args&& ...) [with _Args = {const std::atomic&}, _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::iterator = __gnu_cxx::__normal_iterator<std::atomic*, std::vector<std::atomic > >, typename std::_Vector_base::_Tp_alloc_type::pointer = std::atomic*]' /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from 'void std::vector::push_back(const value_type&) [with _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::value_type = std::atomic]' 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& std::atomic::operator=(const std::atomic&)' /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, _Args = {std::atomic}]': /usr/include/c++/4.6/bits/stl_uninitialized.h:77:3: instantiated from 'static _ForwardIterator std::__uninitialized_copy::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic*>, _ForwardIterator = std::atomic*, 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*>, _ForwardIterator = std::atomic*]' /usr/include/c++/4.6/bits/stl_uninitialized.h:259:63: instantiated from '_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator&) [with _InputIterator = std::move_iterator<std::atomic*>, _ForwardIterator = std::atomic*, _Tp = std::atomic]' /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*, _ForwardIterator = std::atomic*, _Allocator = std::allocator<std::atomic >]' /usr/include/c++/4.6/bits/vector.tcc:343:8: instantiated from 'void std::vector::_M_insert_aux(std::vector::iterator, _Args&& ...) [with _Args = {const std::atomic&}, _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::iterator = __gnu_cxx::__normal_iterator<std::atomic*, std::vector<std::atomic > >, typename std::_Vector_base::_Tp_alloc_type::pointer = std::atomic*]' /usr/include/c++/4.6/bits/stl_vector.h:834:4: instantiated from 'void std::vector::push_back(const value_type&) [with _Tp = std::atomic, _Alloc = std::allocator<std::atomic >, std::vector::value_type = std::atomic]' 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::atomic(const std::atomic&)' /usr/include/c++/4.6/atomic:538:7: error: declared here 

¿Cómo puedo solucionar esto?

El error desaparece cuando comento la línea con push_back() .

editar: edité la publicación … Para aquellos de ustedes que vieron la primera publicación, el error fue vergonzosamente que utilicé gcc lugar de g++ : \

Como se describe en esta pregunta estrechamente relacionada que se mencionó en los comentarios, std::atomic no es copiable ni copiable.

Los tipos de objetos que no tienen estas propiedades no se pueden usar como elementos de std::vector .

Sin embargo, debería ser posible crear un wrapper alrededor del elemento std::atomic que sea copiable y copiable. Tendrá que usar las funciones miembro load() y store() de std::atomic para proporcionar construcción y asignación (esta es la idea descrita por la respuesta aceptada a la pregunta mencionada anteriormente):

 #include  #include  template  struct atomwrapper { std::atomic _a; atomwrapper() :_a() {} atomwrapper(const std::atomic &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> v_a; std::atomic a_i(1); v_a.push_back(a_i); return 0; } 

EDITAR: Como señaló Bo Persson, la operación de copia realizada por el contenedor no es atómica. Le permite copiar objetos atómicos, pero la copia en sí no es atómica. Esto significa que cualquier acceso simultáneo a los átomos no debe hacer uso de la operación de copia. Esto implica que las operaciones en el vector mismo (por ejemplo, agregar o quitar elementos) no deben realizarse simultáneamente.

Ejemplo: si, por ejemplo, un hilo modifica el valor almacenado en uno de los átomos mientras otro hilo agrega nuevos elementos al vector, puede producirse una reasignación de vector y el objeto que el primer hilo modifica puede copiarse de un lugar del vector a otro . En ese caso, habría una carrera de datos entre el acceso al elemento realizado por el primer subproceso y la operación de copia desencadenada por el segundo.

Me parece que atomic no tiene constructor de copia. Ni un constructor de movimientos, por lo que yo sé.

Una vector::emplace_back() podría ser utilizar el vector::emplace_back() para construir el atómico en el lugar en el vector. Por desgracia, ahora mismo no tengo un comstackdor de C ++ 11, o iría a probarlo.

Para responder primero a su pregunta principal: puede declarar un std::vector> fácilmente, como lo ha hecho en su ejemplo.

Sin embargo, debido a la falta de constructores de copia o movimiento para objetos std::atomic<> , su uso del vector será restringido a medida que push_back() el error de comstackción en push_back() . Básicamente no puedes hacer nada que invoque a cualquiera de los constructores.

Esto significa que el tamaño de su vector debe ser fijo en la construcción, y debe manipular los elementos usando operator[] o .at() . Para su código de ejemplo, lo siguiente funciona 1 :

 std::vector> v_a(1); std::atomic a_i(1); v_a[0] = a_i; 

Si la limitación de “tamaño fijo en la construcción” es demasiado onerosa, puede usar std::deque lugar. Esto le permite colocar objetos, hacer crecer la estructura de forma dinámica sin requerir copiar o mover constructores, por ejemplo:

 std::deque> d; d.emplace_back(1); d.emplace_back(2); d.pop_back(); 

Todavía hay algunas limitaciones, sin embargo. Por ejemplo, puede pop_back() , pero no puede usar el erase() más general erase() . Las limitaciones tienen sentido: un erase() en el medio de los bloques de almacenamiento contiguos utilizados por std::deque en general requiere el movimiento de los elementos, por lo tanto, requiere copiar / mover el constructor o los operadores de asignación para estar presentes.

Si no puede vivir con esas limitaciones, podría crear una clase contenedora como se sugiere en otras respuestas, pero tenga en cuenta la implementación subyacente: tiene poco sentido mover un objeto std::atomic<> una vez que se está utilizando: rompería cualquier subproceso que accediera simultáneamente a los objetos. El único uso correcto de los constructores de copia / movimiento generalmente se encuentra en la configuración inicial de colecciones de estos objetos antes de que se publiquen en otros hilos.


1 A menos, tal vez, que utilice el comstackdor icc de Intel, que falla con un error interno al comstackr este código.

Como otros han señalado correctamente, la causa del error del comstackdor es que std :: atomic prohíbe explícitamente el constructor de copias.

Tenía un caso de uso en el que quería la conveniencia de un mapa STL (específicamente, estaba usando un mapa de mapas para lograr una matriz dispersa de atomics bidimensional, así que puedo hacer algo como int val = my_map[10][5] ). En mi caso, solo habría una instancia de este mapa en el progtwig, por lo que no se copiaría, y aún mejor, puedo inicializar todo con la inicialización preparada. Así que fue muy desafortunado que, aunque mi código nunca intentara copiar elementos individuales o el mapa en sí, no pude usar un contenedor STL.

La solución alternativa que finalmente utilicé es almacenar std :: atomic dentro de std :: shared_ptr. Esto tiene pros, pero posiblemente una estafa:

Pros:

  • Puede almacenar std :: atomic dentro de cualquier contenedor STL
  • No requiere / restringe el uso exclusivo de ciertos métodos de contenedores STL.

Pro o Con (la conveniencia de este aspecto depende de los casos de uso de los progtwigs): – Solo hay un único átomo compartido para un elemento dado. Por lo tanto, copiar el shared_ptr o el contenedor STL generará un único atómico compartido para el elemento. En otras palabras, si copia el contenedor STL y modifica uno de los elementos atómicos, el elemento atómico correspondiente del otro contenedor también reflejará el nuevo valor.

En mi caso, la característica Pro / Con era irrelevante debido a mi caso de uso.

Aquí hay una breve syntax para inicializar un std :: vector con este método:

 #include  #include  #include  std::vector > > vecAtomicInts { std::shared_ptr >(new std::atomic(1) ), std::shared_ptr >(new std::atomic(2) ), }; // push_back, emplace, etc all supported vecAtomicInts.push_back(std::shared_ptr >(new std::atomic(3) ) ); // operate atomically on element vecAtomicInts[1]->exchange(4); // access random element int i = *(vecAtomicInts[1]);