vectores sumar resueltos programacion matrices llenar imprimir funciones elementos ejercicios ejemplos con como c++ c++11 stdvector move-semantics

c++ - sumar - ¿Por qué reasignar una copia vectorial en lugar de mover los elementos?



vectores en c++ ejercicios resueltos (3)

Posible duplicado:
¿Cómo hacer cumplir la semántica de movimiento cuando un vector crece?

insert , push_back y push_back ( _back ) pueden causar una reasignación de un std::vector . Me sorprendió ver que el siguiente código copia los elementos en lugar de moverlos mientras reasigna el contenedor.

#include <iostream> #include <vector> struct foo { int value; explicit foo(int value) : value(value) { std::cout << "foo(" << value << ")/n"; } foo(foo const& other) noexcept : value(other.value) { std::cout << "foo(foo(" << value << "))/n"; } foo(foo&& other) noexcept : value(std::move(other.value)) { other.value = -1; std::cout << "foo(move(foo(" << value << "))/n"; } ~foo() { if (value != -1) std::cout << "~foo(" << value << ")/n"; } }; int main() { std::vector<foo> foos; foos.emplace_back(1); foos.emplace_back(2); }

En mi máquina específica que usa mi compilador específico (GCC 4.7) esto imprime lo siguiente:

foo(1) foo(2) foo(foo(1)) ~foo(1) ~foo(1) ~foo(2)

Sin embargo, al eliminar el constructor de copia ( foo(foo const&) = delete; ), se genera la siguiente salida (esperada):

foo(1) foo(2) foo(move(foo(1)) ~foo(1) ~foo(2)

¿Porqué es eso? ¿No sería el movimiento generalmente más eficiente, o al menos no mucho menos eficiente, que copiar?

Cabe señalar que GCC 4.5.1 hace lo esperado : ¿es esto una regresión en GCC 4.7 o es una optimización astutamente inteligente porque el compilador ve que mi objeto es barato de copiar (pero, ¿cómo?).

También tenga en cuenta que me aseguré de que esto sea causado por la reasignación, poniendo experimentalmente un foos.reserve(2); delante de las inserciones; esto hace que ni la copia ni el movimiento se ejecuten.


La respuesta corta es que creo que @BenVoigt es básicamente correcto.

En la descripción de reserve (§23.3.6.3 / 2), dice:

Si se genera una excepción distinta del constructor de movimiento de un tipo que no es CopyInsertable, no hay efectos.

[Y la descripción de cambio de resize en §23.3.6.3 / 12 requiere lo mismo.]

Esto significa que si T es CopyInsertable, obtienes una fuerte excepción de seguridad. Para asegurar eso, solo puede usar construcción de movimiento si deduce (por medios no especificados) que la construcción de movimiento nunca lanzará. No hay garantía de que throw() o noexcept sea ​​necesario o suficiente para eso. Si T es CopyInsertable, simplemente puede optar por utilizar siempre la construcción de copia. Básicamente, lo que está sucediendo es que el estándar requiere una semántica similar a la construcción de copias; el compilador solo puede usar la construcción de movimientos bajo la regla de si-y, y es libre de definir cuándo o si ejercerá esa opción.

Si T no es CopyInsertable, la reasignación usará la construcción de movimientos, pero la excepción de seguridad depende de si el constructor de movimientos de T puede lanzar. Si no se lanza, se obtiene una fuerte excepción de seguridad, pero si se lanza, no (creo que probablemente obtenga la garantía básica, pero tal vez ni siquiera eso y definitivamente no más).


No es una regresión, es una corrección de errores. El estándar especifica que std :: vector solo preferirá un elemento constructor de movimiento que no sea de lanzamiento.

Véase también esta explicación y este informe de error .

Esta pregunta también es relevante.


Punta de tronco clang + libc ++ obtiene:

foo(1) foo(2) foo(move(foo(1)) ~foo(2) ~foo(1)

Si elimina la noexcept del constructor de movimiento, obtendrá la solución de copia:

foo(1) foo(2) foo(foo(1)) ~foo(1) ~foo(2) ~foo(1)