c++ - what is the slope of zero divided by zero
¿Es x=std:: move(x) indefinido? (3)
Llamará a
X::operator = (X&&)
, por lo que depende de la implementación administrar este caso (como se hace para
X::operator = (const X&)
)
Sea
x
una variable de algún tipo que se haya inicializado previamente.
Es la siguiente línea:
x = std::move(x)
indefinido? ¿Dónde está esto en el estándar y qué dice al respecto?
No, este no es un comportamiento indefinido, será un comportamiento definido por la implementación, dependerá de cómo se implemente la asignación de movimiento.
Relevante para esto es el problema 2468 del LWG: asignación automática de tipos de biblioteca , tenga en cuenta que este es un problema activo y no tiene una propuesta oficial, por lo que debe considerarse indicativo en lugar de definitivo, pero señala las secciones involucradas para la biblioteca estándar y señala que actualmente están en conflicto. Dice:
Supongamos que escribimos
vector<string> v{"a", "b", "c", "d"}; v = move(v);
¿Cuál debería ser el estado de v ser? El estándar no dice nada específico sobre la autoasignación de movimientos. Hay texto relevante en varias partes del estándar, y no está claro cómo conciliarlos.
[...]
No está claro en el texto cómo juntar estas piezas, porque no está claro cuál tiene prioridad. Tal vez 17.6.4.9 [res.on.arguments] gana (impone una precondición implícita que no se menciona en los requisitos MoveAssignable, por lo que v = move (v) no está definido), o tal vez 23.2.1 [container.requirements.general ] gana (proporciona explícitamente garantías adicionales para Container :: operator = más allá de lo que está garantizado para las funciones de la biblioteca en general, por lo que v = move (v) es un no-op), o tal vez algo más.
En las implementaciones existentes que verifiqué, por lo que vale, v = move (v) pareció borrar el vector; no dejó el vector sin cambios y no causó un bloqueo.
y propone:
Informalmente: cambie las tablas de requisitos MoveAssignable y Container (y cualquier otra tabla de requisitos que mencione la asignación de movimiento, si corresponde) para hacer explícito que x = move (x) es un comportamiento definido y deja a x en un estado válido pero no especificado. Probablemente eso no sea lo que dice el estándar hoy, pero es probablemente lo que pretendíamos y es coherente con lo que les hemos dicho a los usuarios y con las implementaciones que realmente hacen.
Tenga en cuenta que, para los tipos incorporados, esto es básicamente una copia, podemos ver en el borrador de C ++ 14, sección estándar
5.17
[expr.ass]
:
En la asignación simple (=), el valor de la expresión reemplaza al del objeto al que hace referencia el operando izquierdo.
que es diferente al caso de las clases, donde
5.17
dice:
Si el operando izquierdo es de tipo de clase, la clase estará completa. La asignación a objetos de una clase se define mediante el operador de asignación de copia / movimiento (12.8, 13.5.3).
Tenga en cuenta que clang tiene una advertencia de auto-movimiento :
Registro: agregue una nueva advertencia, -Welf-move, a Clang.
-Welf-move es similar a -Wself-asignar. Esta advertencia se activa cuando se intenta mover un valor a sí mismo. Consulte r221008 para ver un error que habría sido detectado con esta advertencia.
Todo lo que hace es llamar a
X::operator=(X&&)
(con un lvalue calificado "
*this
").
En los tipos primitivos,
std::move
tiene poco interés y no interactúa con
=
en absoluto.
Por lo tanto, esto solo se aplica a objetos de tipo de clase.
Ahora, para un tipo dentro de
std
(o generado por una de sus plantillas), los objetos que se
move
d tienden a quedar en un estado no especificado (pero válido).
Este no es un comportamiento indefinido, pero no es un comportamiento útil.
Tendría que examinarse la semántica de cada
X::operator=(X&&)
dado, examinar cada tipo dentro de
std
sería "demasiado amplio" para una respuesta de desbordamiento de pila.
Incluso pueden contradecirse a sí mismos.
En general, cuando se
move
desde un objeto, le está comunicando al consumidor que "no le importa en qué estado se encuentre el objeto después".
El uso de a
x = std::move(x)
es, por lo tanto, descortés, ya que (por lo general) le importa en qué estado se encuentra
x
después de que se completa la operación (como le está asignando).
Está utilizando el mismo objeto como lvalue y rvalue dentro de la misma operación, y esa no es una buena práctica.
Una excepción interesante es el estándar
std::swap
, que se parece a esto:
template<class T>
void swap(T& lhs, T& rhs) {
T tmp = std::move(lhs);
lhs = std::move(rhs);
rhs = std::move(tmp);
}
la línea media,
lhs = std::move(rhs)
, hace una
x = std::move(x)
si llama a swap dos veces en el mismo objeto.
Sin embargo, tenga en cuenta que no nos importa en qué estado se encuentre
x
después de completar esta línea;
ya hemos almacenado el estado de
x
en
tmp
, y lo restauraremos en la siguiente línea.