En C ++ 11, podemos escribir este código:
struct Cat {
Cat(){}
};
const Cat cat;
std::move(cat); //this is valid in C++11
cuando llamo std::move
, significa que quiero mover el objeto, es decir, cambiaré el objeto. Mover un const
objeto no es razonable, entonces, ¿por qué std::move
no restringe este comportamiento? Será una trampa en el futuro, ¿verdad?
Aquí trampa significa como Brandon mencionó en el comentario:
"Creo que lo que quiere decir es que lo" atrapa "furtivamente, porque si no se da cuenta, termina con una copia que no es lo que pretendía".
En el libro 'Effective Modern C ++' de Scott Meyers, da un ejemplo:
class Annotation {
public:
explicit Annotation(const std::string text)
: value(std::move(text)) //here we want to call string(string&&),
//but because text is const,
//the return type of std::move(text) is const std::string&&
//so we actually called string(const string&)
//it is a bug which is very hard to find out
private:
std::string value;
};
Si std::move
estuviera prohibido operar en un const
objeto, podríamos descubrir fácilmente el error, ¿verdad?
std::move
por sí solo no le hace nada al objeto. Se podría argumentar questd::move
está mal nombrado.CAT cat2 = std::move(cat);
, asumiendo queCAT
admite la asignación de movimiento regular.std::move
es solo un yeso, en realidad no mueve nadaRespuestas:
aquí vemos un uso de
std::move
en aT const
. Devuelve unT const&&
. Tenemos un constructor de movimientosstrange
que toma exactamente este tipo.Y se llama.
Ahora bien, es cierto que este tipo extraño es más raro que los errores que solucionaría tu propuesta.
Pero, por otro lado, el existente
std::move
funciona mejor en código genérico, donde no sabes si el tipo con el que estás trabajando es aT
o aT const
.fuente
std::move
a unconst
objeto.const T&&
. Esto expresa un "protocolo API" del tipo "Tomaré un rvalue-ref pero prometo que no lo modificaré". Supongo que, aparte de cuando se usa mutable, es poco común. Quizás otro caso de uso es poder usarloforward_as_tuple
en casi cualquier cosa y luego usarlo.Hay un truco aquí que estás pasando por alto, es decir, que en
std::move(cat)
realidad no mueve nada . Simplemente le dice al compilador que intente moverse. Sin embargo, dado que su clase no tiene un constructor que acepte aconst CAT&&
, en su lugar utilizará elconst CAT&
constructor de copia implícito y copiará de forma segura. Sin peligro, sin trampa. Si el constructor de copia está deshabilitado por cualquier motivo, obtendrá un error de compilador.impresiones
COPY
, noMOVE
.http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f
Tenga en cuenta que el error en el código que menciona es un problema de rendimiento , no un problema de estabilidad , por lo que dicho error nunca causará un bloqueo. Solo usará una copia más lenta. Además, este error también ocurre para objetos no constantes que no tienen constructores de movimiento, por lo que simplemente agregar una
const
sobrecarga no los detectará a todos. Podríamos comprobar la capacidad de mover construir o mover asignar desde el tipo de parámetro, pero eso interferiría con el código de plantilla genérico que se supone que recurre al constructor de copia. Y diablos, tal vez alguien quiera poder construirconst CAT&&
, ¿quién soy yo para decir que no puede?fuente
const
l tampoco ayudará. [class.copy] §8: "De lo contrario, el constructor de copia declarado implícitamente tendrá la formaX::X(X&)
"Una razón por la que el resto de las respuestas se ha pasado por alto hasta ahora es la capacidad del código genérico para ser resistente frente al movimiento. Por ejemplo, digamos que quería escribir una función genérica que moviera todos los elementos de un tipo de contenedor para crear otro tipo de contenedor con los mismos valores:
Genial, ahora puedo crear de forma relativamente eficiente un
vector<string>
desde adeque<string>
y cada individuo sestring
moverá en el proceso.Pero, ¿y si quiero pasar de un
map
?Si se
std::move
insiste en un noconst
argumento, la instanciación anterior demove_each
no se compilaría porque está intentando mover aconst int
(elkey_type
demap
). Pero a este código no le importa si no puede mover elkey_type
. Quiere mover elmapped_type
(std::string
) por motivos de rendimiento.Es para este ejemplo, y otros innumerables ejemplos como este en la codificación genérica, que
std::move
es una solicitud para moverse , no una demanda para moverse.fuente
Tengo la misma preocupación que el OP.
std :: move no mueve un objeto, ni garantiza que el objeto sea movible. Entonces, ¿por qué se llama mover?
Creo que no ser movible puede ser uno de los siguientes dos escenarios:
1. El tipo móvil es constante.
La razón por la que tenemos la palabra clave const en el lenguaje es que queremos que el compilador evite cualquier cambio en un objeto definido como const. Dado el ejemplo en el libro de Scott Meyers:
¿Qué significa literalmente? Mueva una cadena constante al miembro de valor; al menos, eso es lo que entiendo antes de leer la explicación.
Si el lenguaje tiene la intención de no mover o no garantizar que el movimiento sea aplicable cuando se llama a std :: move (), entonces es literalmente engañoso cuando se usa la palabra mover.
Si el lenguaje está animando a las personas que usan std :: move a tener una mayor eficiencia, tiene que prevenir trampas como esta lo antes posible, especialmente para este tipo de contradicción literal obvia.
Estoy de acuerdo en que la gente debe saber que mover una constante es imposible, pero esta obligación no debe implicar que el compilador pueda guardar silencio cuando ocurre una contradicción obvia.
2. El objeto no tiene constructor de movimiento
Personalmente, creo que esta es una historia separada de la preocupación de OP, como dijo Chris Drew.
fuente
Me sorprende que nadie haya mencionado el aspecto de compatibilidad con versiones anteriores de esto. Creo que
std::move
fue diseñado a propósito para hacer esto en C ++ 11. Imagine que está trabajando con una base de código heredada, que depende en gran medida de las bibliotecas de C ++ 98, por lo que sin el respaldo de la asignación de copia, la mudanza rompería las cosas.fuente
Afortunadamente, puede usar el cheque de clang-tidy para encontrar tales problemas: https://clang.llvm.org/extra/clang-tidy/checks/performance-move-const-arg.html
fuente