Estoy revisando un código como este, donde A
es un tipo móvil:
// Returns true exactly when ownership of a is taken
bool MaybeConsume(A&& a) {
if (some condition) {
Consume(std::move(a)); // ???
return true;
}
return false;
}
// ... elsewhere ...
A a;
if (!MaybeConsume(std::move(a))) {
a.DoSomething(); // !!!
}
Nuestra herramienta de análisis estático se queja de que a
se usa después de ser movido (en !!!
). IIUC std::move
es solo un static_cast
, y el objeto a
no se destripará hasta que se llame a un constructor de movimientos o un operador de asignación (presumiblemente en Consume
). Suponiendo que MaybeConsume
satisface el contrato en el comentario,
- ¿Esto funciona?
- ¿Es UB?
- ¿
std::move
en???
no tiene operación?
(Probablemente esta instancia en particular pueda refactorizarse para evitar la sutileza, pero aún me gustaría pedir mi propia comprensión).
2 respuestas
Es una advertencia falsa de su herramienta de análisis estático.
- ¿Funciona?
Sí, MaybeConsume
está haciendo lo que dice el comentario. Solo se está apropiando de su argumento cuando some condition
es verdadero (asumiendo que Consume
realmente mueve la construcción / asignación de su argumento).
std::move
es realmente una fantasía static_cast<T&&>
así que { {X2}} no está transfiriendo la propiedad, simplemente está vinculando una referencia al parámetro de MaybeConsume
.
- ¿Es UB?
No, no está utilizando a
si MaybeConsume
indica que ha asumido la propiedad de su argumento.
- ¿
std::move
en ??? no tiene operación?
Bueno, no es una operación porque es solo un static_cast
, pero si quería preguntar si es innecesario, entonces no, no lo es. Dentro del cuerpo de MaybeConsume
, a
es un lvalue porque tiene un nombre. Si la firma de Consume
es void Consume(A&&)
, entonces el código no se compilará sin ese std::move
.
A partir del uso de ejemplo que ha mostrado, parece que no se supone que debe llamar a MaybeConsume
con un argumento prvalue, ya que la persona que llama debería usar el argumento de alguna otra manera si la función devuelve false
. Si eso es cierto, entonces debería cambiar su firma a bool MaybeConsume(A&)
. Esto probablemente hará feliz a su herramienta de análisis estático porque le permitirá escribir if (!MaybeConsume(a))
.
Para comprender por qué la herramienta de análisis estático genera una advertencia, es necesario pensar en la forma en que lo hace un analizador estático. Cuando ve un fragmento de código como el siguiente:
A a;
fun(std::move(a);
a.method();
No está claro qué podría suceder dentro de la llamada fun (). Para ejecutar con éxito el método () en un depende que se cumplan algunos requisitos previos, que pueden no (o ya no) se cumplen después de la llamada de fun (). Si bien el programador bien puede saber que es seguro llamar al método (), el analizador no, por lo que genera una advertencia.
La siguiente es solo mi propia opinión. Es más seguro simplemente asumir que la propiedad de a está totalmente tomada por diversión (). Para evitar confusiones, es mejor imponer un estilo de pedir prestado y devolver. Pensando en ello como si un amigo te pidiera prestado un libro, no puedes (no puedes) usar ese libro hasta que te lo devuelvan. Por lo tanto, nunca se arriesgue a invocar accidentalmente un objeto que debería estar "muerto" para entonces.
Vea el código de demostración a continuación:
#include <iostream>
#include <utility>
#include <tuple>
#include<cassert>
struct A {
public:
int *p;
public:
A() {
p = new int();
assert(p != nullptr);
std::cout << p << std::endl;
std::cout << "default constrctor is called" << std::endl;
}
A(const A&) = delete;
A& operator=(const A&) = delete;
A(A&& _a): p(_a.p) {
_a.p = nullptr;
std::cout << p << std::endl;
std::cout << "move constructor is called" << std::endl;;
}
A& operator=(A&& _a) {
std::cout << "move assignment is called"<<std::endl;;
p = std::move(_a.p);
return *this;
}
void DoSomthing(){
std::cout << "do somthing is called" << std::endl;
*p = 100;
std::cout << "value of p is changed"<<std::endl;
}
};
std::tuple<A&&, bool> MaybeConsume(A&& a) {
if (1==2) {//try 1==1 alternatively
delete a.p;
a.p = nullptr;//consume
std::cout << "content consumed" << std::endl;
return std::make_tuple(Consume(std::move(a)), true);
}
else {
return std::make_tuple(std::move(a), false);
}
}
int main()
{
A a;
std::tuple<A&&, bool> t = MaybeConsume(std::move(a));
if (!(std::get<bool> (t))) {
A a1 = std::move(std::get<A&&>(t));
a1.DoSomthing();
}
return 0;
}
Nuevas preguntas
c++
C ++ es un lenguaje de programación de propósito general. Originalmente fue diseñado como una extensión de C y tiene una sintaxis similar, pero ahora es un lenguaje completamente diferente. Utilice esta etiqueta para preguntas sobre el código (a ser) compilado con un compilador C ++. Utilice una etiqueta de versión específica para preguntas relacionadas con una revisión estándar específica [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] o [C ++ 23], etc. .