web-dev-qa-db-fra.com

Objet passé à std :: move mais non déplacé de?

Je vérifie un code comme celui-ci, où A est un type déplaçable:

// 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();  // !!!
}

Notre outil d'analyse statique se plaint que a est utilisé après avoir été déplacé (à !!!). IIUC std::move est seulement un static_cast, et l'objet a ne sera en réalité pas vidé avant qu'un constructeur de déplacement ou un opérateur d'affectation soit appelé (probablement dans Consume). En supposant que MaybeConsume satisfasse le contrat dans le commentaire,

  1. Est-ce que ça marche?
  2. Est-ce UB?
  3. std::move at ??? est-il un no-op?

(Il est probable que cette instance particulière peut être refondue pour éviter la subtilité, mais j'aimerais quand même demander ma propre compréhension).

23
stewbasic

C'est un avertissement parasite de votre outil d'analyse statique.

  1. Est-ce que ça marche?

Oui, MaybeConsume fait ce que dit le commentaire. Il ne prend possession de son argument que lorsque some condition est vrai (en supposant que Consume déplace effectivement la construction/assign de son argument).

std::move est en effet juste un fantaisie static_cast<T&&>, donc MaybeConsume(std::move(a)) ne transfère pas le droit de propriété, vous liez simplement une référence au paramètre MaybeConsume.

  1. Est-ce UB?

Non, vous n'utilisez pas a si MaybeConsume indique qu'il a assumé la propriété de son argument.

  1. Est-ce que std::move est à ??? un no-op?

Eh bien, c'est un non-op parce que c'est juste un static_cast, mais si vous vouliez demander si c'est inutile, alors non, ça ne l'est pas. Dans le corps de MaybeConsume, a est une lvalue car elle porte un nom . Si la signature de Consume est void Consume(A&&), le code ne sera pas compilé sans ce std::move.


Dans l’exemple d’utilisation que vous avez montré, il semble que vous n’êtes pas censé appeler MaybeConsume avec un argument prvalue, car l’appelant devrait probablement utiliser l’argument d’une autre manière si la fonction renvoie false. Si c'est vrai, vous devriez alors changer sa signature en bool MaybeConsume(A&). Cela rendra probablement votre outil d'analyse statique heureux car cela vous permettrait d'écrire if (!MaybeConsume(a)).

14
Praetorian

Pour comprendre pourquoi l'outil d'analyse statique émet un avertissement, il est nécessaire de penser à la manière d'un analyseur statique. Quand il voit un morceau de code comme ci-dessous:

A a;
fun(std::move(a);
a.method();

On ne sait pas ce qui pourrait se passer dans l'appel de fun (). Pour exécuter avec succès method () sur un élément, il faut que certaines conditions préalables soient remplies, ce qui pourrait ne pas (ou plus) être maintenu après l'appel de fun (). Alors que le programmeur sait peut-être qu'il est sûr d'appeler method (), l'analyseur ne le sait pas, il déclenche donc un avertissement.

Ce qui suit est seulement ma propre opinion. Il est plus sûr d'assumer que la propriété d'un est totalement prise par fun (). Pour éviter toute confusion, il est préférable d'appliquer un style d'emprunt-retour. Si vous le considérez comme si un ami vous empruntait un livre, vous ne pouvez pas (ne pouvez pas) utiliser ce livre tant qu'il n'a pas été rendu. Ainsi, ne vous risquez jamais d'invoquer accidentellement un objet qui devrait être "mort" à ce moment-là.

Voir le code de démonstration ci-dessous:

#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;
}
0
John Z. Li