2010-05-20 5 views
42

Estoy confundido con la filosofía unique_ptr y rvalue move.¿Se puede usar unique_ptr de forma segura en las colecciones stl?

Digamos que tenemos dos colecciones:

std::vector<std::auto_ptr<int>> autoCollection; 
std::vector<std::unique_ptr<int>> uniqueCollection; 

Ahora que se puede esperar el siguiente al fracaso, ya que no se sabe lo que el algoritmo está haciendo internamente y tal vez la realización de copias de articulación interna y similares, arrancando por lo tanto propiedad de distancia de auto_ptr:

std::sort(autoCollection.begin(), autoCollection.end()); 

Entiendo esto. Y el compilador con razón no permite que esto suceda.

Pero entonces hacer 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 algo crucial de información, así que espero ansiosamente que alguien me proporcione el aha! momento.

+1

En realidad, el compilador * debería * quejarse de 'std :: vector > autoCollection;' porque los COAPS (Contenedores de punteros automáticos) no están permitidos en ninguna descripción. –

+0

Usando VS2010. Ni siquiera recibo una advertencia en/W4. – DanDan

+1

u obtén una advertencia de que auto_ptr está depreciado. – DanDan

Respuesta

50

creo que es más una cuestión de la filosofía de la técnica :)

La pregunta de fondo es ¿cuál es la diferencia entre Mover y copiar. No voy a saltar en el lenguaje/standardista técnica, vamos a hacer de manera simple:

  • Copia: crear otro objeto idéntico (o, al menos, una que debe comparar iguales)
  • Mover: tomar un objeto y ponerlo en otra ubicación

Como dijiste, es posible implementar Mover en el plazo de la 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 Mover adecuada resuelve los 2 números:

  • Está claro qué objeto tiene la propiedad: la nueva, ya que se descartará el original
  • lo tanto, es necesario copiar los recursos señalaron , lo que permite una mayor eficiencia

El auto_ptr y unique_ptr son un muy buen ejemplo de esto.

Con un auto_ptr tiene una copia semántica atornillada: el original y la copia no son 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, el unique_ptr es exactamente eso: garantiza un propietario único del recurso, evitando así la copia y el inevitable problema de eliminación que seguiría. 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) 

Así que puede usarunique_ptr en un recipiente (a diferencia de auto_ptr), pero una serie de operaciones será imposible porque implican la copia, que el tipo no es compatible.

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 deshabilitar para garantizar la portabilidad del código ... no lo use para verificar el estándar :)

+0

Gracias, excelente respuesta y ejemplos :) – DanDan

+3

En su comentario "bien, porque usar Move constructor" quería decir "mover asignación o' swap' "? Además, para completar, así es como el 'imposibles' se podría hacer uso de 'std :: move' y' std :: make_move_iterator': http://coliru.stacked-crooked.com/a/6b032d70a9ed6f5c – sehe

+0

@sehe: eres bien, me refería al Operador de Asignación de Movimientos. –

11

Las unique_ptr s se están moviendo utilizando su constructor de movimiento. unique_ptr es movible, pero no CopyConstructable.

Hay un gran artículo sobre las referencias rvalue here. Si aún no ha leído sobre ellos, o está confundido, ¡eche un vistazo!

+0

Hey gracias por el gran artículo! se puso un poco loco alrededor de la página 7, pero aunque he aprendido mucho. pero mi problema original permanece. ¿por qué los movimientos seguros todavía copias no son? ¿no es un unique_ptr a mover el mismo que la copia de un auto_ptr? Si el movimiento constructor del objeto está utilizando std :: movimiento, es decir, la transferencia de la propiedad, no es este el comportamiento por defecto de una copia auto_ptr? – DanDan

+1

Sí, pero auto_ptr fue de delante de C++ 0x y no está expresamente permitido en contenedores. Por otro lado, dado que unique_ptr se puede mover con seguridad, usted (y el compilador) pueden estar seguros de que la propiedad no se ha violado. Además, tenga en cuenta que el algoritmo de ordenación de STL no está especificado, y en C++ 0x solo se necesita mover las cosas. Sin embargo, es posible hacer una quicksort in situ, por lo que no se requieren copias. – rlbond

7

std::sort podría funcionar solo con operaciones de movimiento y sin copia, siempre que haya una sola copia activa de cada objeto en un momento dado. 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>> que excede su capacidad, asigna almacenamiento para un vector más grande y luego mueve todos los objetos del almacenamiento anterior al nuevo. Esta no es una operación local, pero es perfectamente válida.

Pues resulta que, como algoritmos de ordenación rápida-tipo y el montón-tipo puede en el trabajo hecho 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.

Cuestiones relacionadas