c++ - smart - Entonces, ¿unique_ptr se puede utilizar de forma segura en las colecciones stl?
shared_ptr (3)
Creo que es más una cuestión de filosofía que de técnica :)
La pregunta subyacente es cuál es la diferencia entre Mover y Copiar. No saltaré al lenguaje técnico / estándar, hagámoslo simplemente:
- Copiar: crear otro objeto idéntico (o al menos, uno que DEBE comparar igual)
- Mover: tomar un objeto y ponerlo en otra ubicación
Como dijiste, es posible implementar Mover en el término de Copia: crea una copia en la nueva ubicación y descarta el original. Sin embargo, hay dos problemas allí. Uno es de rendimiento, el segundo es sobre los objetos utilizados para RAII: ¿cuál de los dos debe tener la propiedad?
Un constructor Move adecuado resuelve los 2 problemas:
- Está claro qué objeto tiene propiedad: el nuevo, ya que el original se descartará
- Por lo tanto, no es necesario copiar los recursos apuntados, lo que permite una mayor eficiencia
El auto_ptr
y unique_ptr
son una muy buena ilustración de esto.
Con un auto_ptr
tienes una copia semántica atornillada: el original y la copia no se comparan iguales. Podría usarlo para su semántica de movimiento, pero existe el riesgo de que pierda el objeto apuntado a alguna parte.
Por otro lado, unique_ptr
es exactamente eso: garantiza un propietario único del recurso, evitando así la copia y el inevitable problema de eliminación que se produciría a continuación. Y la no copia también está garantizada en tiempo de compilación. Por lo tanto, es adecuado en contenedores siempre que no intente tener inicialización de copia.
typedef std::unique_ptr<int> unique_t;
typedef std::vector< unique_t > vector_t;
vector_t vec1; // fine
vector_t vec2(5, unique_t(new Foo)); // Error (Copy)
vector_t vec3(vec1.begin(), vec1.end()); // Error (Copy)
vector_t vec3(make_move_iterator(vec1.begin()), make_move_iterator(vec1.end()));
// Courtesy of sehe
std::sort(vec1.begin(), vec1.end()); // fine, because using Move Assignment Operator
std::copy(vec1.begin(), vec1.end(), std::back_inserter(vec2)); // Error (copy)
De modo que puede usar unique_ptr
en un contenedor (a diferencia de auto_ptr
), pero varias operaciones serán imposibles porque implican copias que el tipo no admite.
Desafortunadamente Visual Studio puede ser bastante laxo en la aplicación del estándar y también tiene una serie de extensiones que necesitaría desactivar para garantizar la portabilidad del código ... no lo use para verificar el estándar :)
Estoy confundido con la filosofía de movimiento unique_ptr y rvalue.
Digamos que tenemos dos colecciones:
std::vector<std::auto_ptr<int>> autoCollection;
std::vector<std::unique_ptr<int>> uniqueCollection;
Ahora esperaría que lo siguiente fallara, ya que no se sabe lo que el algoritmo está haciendo internamente y tal vez hacer copias pivote internas y cosas por el estilo, lo que despoja a la propiedad del auto_ptr:
std::sort(autoCollection.begin(), autoCollection.end());
Entiendo esto. Y el compilador con razón no permite que esto suceda.
Pero luego hago esto:
std::sort(uniqueCollection.begin(), uniqueCollection.end());
Y esto compila. Y no entiendo por qué. No creo que se puedan copiar unique_ptrs. ¿Esto significa que no se puede tomar un valor de pivote, por lo que el tipo es menos eficiente? ¿O es este pivote en realidad un movimiento, que de hecho es tan peligroso como la colección de auto_ptrs, y el compilador no debería permitirlo?
Creo que me falta información crucial, así que espero ansiosamente a que alguien me proporcione el aha! momento.
Los unique_ptr
s se están moviendo usando su constructor de movimiento. unique_ptr
es Movable, pero no CopyConstructable.
Hay un excelente artículo sobre referencias de valores here . Si aún no ha leído sobre ellos, o está confundido, ¡eche un vistazo!
std::sort
solo podría funcionar con operaciones de movimiento y sin copia, siempre y cuando solo haya una copia en vivo de cada objeto en un momento determinado. Este es un requisito más débil que trabajar en el lugar, ya que en principio se puede asignar otra matriz temporalmente y mover todos los objetos para reordenarlos.
por ejemplo, con std::vector<std::unique_ptr<T>>
excediendo su capacidad, asigna almacenamiento para un vector más grande y luego mueve todos los objetos del almacenamiento antiguo al nuevo. Esta no es una operación local, pero es perfectamente válida.
Como resultado, los algoritmos de clasificación como quick-sort y heap-sort pueden de hecho funcionar en el lugar sin dificultad. La rutina de partición quick-sort usa std :: swap internamente, que cuenta como una operación de movimiento para ambos objetos involucrados. Al seleccionar un pivote, un truco es intercambiarlo con el primer elemento en el rango, de esta manera nunca se moverá hasta que se termine la partición.