c++ - ¿Cómo almacenar objetos sin copiar o mover constructor en std:: vector?
std:: move c++ 11 (4)
Para mejorar la eficiencia de std::vector<T>
, su matriz subyacente debe asignarse previamente y, a veces, debe asignarse nuevamente. Sin embargo, esto requiere la creación y posterior traslado de objetos de tipo T
con un ctor de copia o ctor de movimiento.
El problema que tengo es que T
no se puede copiar o mover porque contiene objetos que no se pueden copiar o mover (como atomic
y mutex
). (Y, sí, estoy implementando un grupo de subprocesos simple.)
Me gustaría evitar el uso de punteros porque:
- No necesito un nivel de direccionamiento indirecto y por eso no quiero uno.
- (Los punteros son menos eficientes y aumentan la complejidad. El uso de los punteros aumenta la fragmentación de la memoria y disminuye la ubicación de los datos, lo que puede (pero no necesariamente) causar un impacto notable en el rendimiento. No es tan importante, pero vale la pena tener en cuenta)
¿Hay alguna manera de evitar un nivel de direccionamiento indirecto aquí?
ACTUALIZACIÓN: arreglé algunas suposiciones incorrectas y volví a redactar la pregunta, en base a los comentarios de los comentarios y respuestas.
Sin embargo, esto requiere la creación de objetos de tipo T con un ctor de copia.
Eso no es del todo correcto, como en C ++ 11, si usa el constructor de std::vector
que construirá una serie de elementos por defecto, entonces no necesita tener un constructor de copia o movimiento.
Como tal, si no se agregan o eliminan subprocesos de su grupo, puede simplemente hacer:
int num = 23;
std::vector<std::mutex> vec(num);
Si desea agregar o eliminar cosas dinámicamente, entonces tiene que usar un direccionamiento indirecto.
- Use
std::vector
+std::unique_ptr
como ya se propuso - Use un
std::deque
, que le permite usarlo ordenadamente con un rango basado en bucles o algoritmos estándar, y evita todas las indirecciones. (Lo que solo permite adiciones) - Use un
std::list/forward_list
esta solución es similar a la número uno, sin embargo, tiene el beneficio adicional de un uso más fácil con un rango basado en algoritmos. Probablemente sea mejor si solo está accediendo a los elementos de forma secuencial, ya que no hay soporte para el acceso aleatorio.
Me gusta esto:
std::deque<std::mutex> deq;
deq.emplace_back();
deq.emplace_back();
for(auto& m : deq) {
m.lock();
}
Como nota final, std::thread
es, por supuesto, movible, por lo que puede usar std::vector
+ std::vector::emplace_back
con él.
Para comenzar, std::mutex
no se puede copiar ni mover, por lo tanto, se ve obligado a usar algún tipo de direccionamiento indirecto.
Ya que quiere almacenar mutex en un vector, y no copiarlo, usaría std::unique_ptr
.
vector<unique_ptr<T>>
no permite ciertas operaciones vectoriales (como for_each)
No estoy seguro de entender esa frase. Es perfectamente posible hacer rango para:
std::vector< std::unique_ptr< int > > v;
// fill in the vector
for ( auto & it : v )
std::cout << *it << std::endl;
o usar algoritmos estándar:
#include <iostream>
#include <typeinfo>
#include <vector>
#include <memory>
#include <algorithm>
int main()
{
std::vector< std::unique_ptr< int > > v;
v.emplace_back( new int( 3 ) );
v.emplace_back( new int( 5 ) );
std::for_each( v.begin(), v.end(), []( const std::unique_ptr< int > & it ){ std::cout << *it << std::endl; } );
}
Para resumir lo propuesto hasta ahora:
- Usar
vector<unique_ptr<T>>
: agrega un nivel explícito de indirección y no fue deseado por OP. - Usar
deque<T>
: primero probé eldeque
, pero el borrado de objetos tampoco funciona. Vea esta discusión sobre las diferencias entredeque
ylist
.
La solución es usar forward_list
que es una lista enlazada individualmente (o puede usar la list
si desea una lista enlazada doblemente). Como señaló @JanHudec, el vector
(y muchos de sus amigos) requieren una reasignación al agregar o eliminar elementos. Eso no encaja bien con objetos como mutex
y atomic
que no se pueden copiar ni mover. forward_list
y list
no requieren eso porque cada celda se asigna de forma independiente (no puedo citar el estándar en eso, pero el método de indexación da lugar a esa suposición). Dado que en realidad son listas vinculadas, no admiten la indexación de acceso aleatorio. myList.begin() + i
obtendré un iterador del elemento i
''th, pero (seguramente) tendrá que recorrer primero todas las celdas i
anteriores.
No he visto las promesas del estándar, pero las cosas funcionan bien en Windows (Visual Studio) y CompileOnline (g ++). Siéntase libre de jugar con el siguiente caso de prueba en CompileOnline :
#include <forward_list>
#include <iostream>
#include <mutex>
#include <algorithm>
using namespace std;
class X
{
/// Private copy constructor.
X(const X&);
/// Private assignment operator.
X& operator=(const X&);
public:
/// Some integer value
int val;
/// An object that can be neither copied nor moved
mutex m;
X(int val) : val(val) { }
};
int main()
{
// create list
forward_list<X> xList;
// add some items to list
for (int i = 0; i < 4; ++i)
xList.emplace_front(i);
// print items
for (auto& x : xList)
cout << x.val << endl;
// remove element with val == 1
// WARNING: Must not use remove here (see explanation below)
xList.remove_if([](const X& x) { return x.val == 1; });
cout << endl << "Removed ''1''..." << endl << endl;
for (auto& x : xList)
cout << x.val << endl;
return 0;
}
Salida:
Executing the program....
$demo
3
2
1
0
Removed ''1''...
3
2
0
Espero que esto tenga aproximadamente el mismo rendimiento que vector<unique_ptr<T>>
(siempre y cuando no utilice la indexación de acceso aleatorio con demasiada frecuencia).
ADVERTENCIA : el uso de forward_list::remove
no funciona actualmente en VS 2012. Esto se debe a que copia el elemento antes de intentar eliminarlo. El archivo de encabezado Microsoft Visual Studio 11.0/VC/include/forward_list
(mismo problema en la list
) revela:
void remove(const _Ty& _Val_arg)
{ // erase each element matching _Val
const _Ty _Val = _Val_arg; // in case it''s removed along the way
// ...
}
Por lo tanto, se copia "en caso de que se elimine en el camino". Esto significa que list
y forward_list
ni siquiera permiten almacenar unique_ptr
. Supongo que esto es un error de diseño.
La remove_if
es simple: tienes que usar remove_if
lugar de remove
porque la implementación de esa función no copia nada.
Mucho del crédito va a las otras respuestas. Sin embargo, como ninguno de ellos era una solución completa sin punteros, decidí escribir esta respuesta.
Puede almacenar elementos sin mover o copiar constructores en std::vector
, pero simplemente debe evitar los métodos que requieren que los elementos tengan constructores de mover o copiar. Casi 1 cualquier cosa que cambie el tamaño del vector (por ejemplo, push_back
, push_back
resize()
, etc.).
En la práctica, esto significa que debe asignar un vector de tamaño fijo en el momento de la construcción, que invocará al constructor predeterminado de sus objetos, que puede modificar con la asignación. Esto podría al menos funcionar para std::atomic<>
objetos std::atomic<>
, que pueden asignarse a.
1 clear()
es el único ejemplo de un método de cambio de tamaño que no requiere constructores de copiar / mover, ya que nunca necesita mover o copiar ningún elemento (después de todo, el vector está vacío después de esta operación). Por supuesto, nunca más podrás hacer que tu vector de tamaño cero vuelva a crecer después de llamar a esto.