web-dev-qa-db-fra.com

Pourquoi pouvons-nous utiliser `std :: move` sur un objet` const`?

En C++ 11, nous pouvons écrire ce code:

struct Cat {
   Cat(){}
};

const Cat cat;
std::move(cat); //this is valid in C++11

lorsque j’appelle std::move, cela signifie que je veux déplacer l’objet, c’est-à-dire que je vais le changer. Déplacer un objet const est déraisonnable, alors pourquoi std::move ne limite-t-il pas ce comportement? Ce sera un piège à l'avenir, non?

Ici, piège signifie comme Brandon mentionné dans le commentaire:

"Je pense qu'il veut dire que ça" le piège "le furtivement, parce que s'il ne le fait pas, réalisez-vous, il se retrouve avec une copie qui n'est pas ce qu'il voulait."

Dans le livre "Effective Modern C++" de Scott Meyers, il donne un exemple:

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 était interdit d'opérer sur un objet const, nous pourrions facilement trouver le bogue, n'est-ce pas?

83
camino
struct strange {
  mutable size_t count = 0;
  strange( strange const&& o ):count(o.count) { o.count = 0; }
};

const strange s;
strange s2 = std::move(s);

nous voyons ici une utilisation de std::move sur un T const. Il retourne un T const&&. Nous avons un constructeur de mouvements pour strange qui prend exactement ce type.

Et ça s'appelle.

Il est vrai que ce type étrange est plus rare que les problèmes que votre proposition corrigerait.

Par contre, le std::move existant fonctionne mieux en code générique, où vous ne savez pas si le type avec lequel vous travaillez est un T ou un T const.

44

Vous oubliez un truc, à savoir que std::move(cat)ne déplace rien en fait}. Il indique simplement au compilateur de essayer de se déplacer. Cependant, étant donné que votre classe n'a pas de constructeur acceptant un const CAT&&, elle utilisera le constructeur de copie implicite const CAT& et la copie en toute sécurité. Pas de danger, pas de piège. Si le constructeur de la copie est désactivé pour une raison quelconque, vous obtiendrez une erreur du compilateur.

struct CAT
{
   CAT(){}
   CAT(const CAT&) {std::cout << "COPY";}
   CAT(CAT&&) {std::cout << "MOVE";}
};

int main() {
    const CAT cat;
    CAT cat2 = std::move(cat);
}

empreintes COPY, pas MOVE.

http://coliru.stacked-crooked.com/a/0dff72133dbf9d1f

Notez que le bogue dans le code que vous mentionnez est un problème performance, pas un problème {stabilité}, donc un tel bogue ne causera jamais de crash. Il utilisera simplement une copie plus lente. En outre, un tel bogue survient également pour les objets non-const qui n'ont pas de constructeur de déplacement. Par conséquent, le simple ajout d'une surcharge const ne les prendra pas tous. Nous pourrions vérifier la capacité de déplacer construction ou déplacer assign du type de paramètre, mais cela interférerait avec le code de modèle générique qui est supposé revenir sur le constructeur de copie . veut pouvoir construire à partir de const CAT&&, à qui dois-je dire qu'il ne le peut pas?

87
Mooing Duck

Une des raisons pour lesquelles le reste des réponses a été négligé jusqu’à présent est la capacité du code generic à être résilient face au déménagement. Par exemple, disons que je voulais écrire une fonction générique qui déplaçait tous les éléments d'un type de conteneur pour créer un autre type de conteneur avec les mêmes valeurs:

template <class C1, class C2>
C1
move_each(C2&& c2)
{
    return C1(std::make_move_iterator(c2.begin()),
              std::make_move_iterator(c2.end()));
}

Cool, je peux maintenant créer de manière relativement efficace un vector<string> à partir d'un deque<string> et chaque personne string sera déplacée dans le processus.

Mais que se passe-t-il si je veux passer d'une map?

int
main()
{
    std::map<int, std::string> m{{1, "one"}, {2, "two"}, {3, "three"}};
    auto v = move_each<std::vector<std::pair<int, std::string>>>(m);
    for (auto const& p : v)
        std::cout << "{" << p.first << ", " << p.second << "} ";
    std::cout << '\n';
}

Si std::move insistait sur un argument non -const, l'instanciation ci-dessus de move_each ne serait pas compilée car elle tentait de déplacer un const int (le key_type de la map). Mais ce code ne se soucie pas s'il ne peut pas déplacer le key_type. Il souhaite déplacer le mapped_type (std::string) pour des raisons de performances.

C'est pour cet exemple et d'innombrables autres exemples comme celui-ci dans le codage générique que std::move est une requête pour déplacer , pas une demande pour déplacer.

19
Howard Hinnant

J'ai le même souci que le PO.

std :: move ne déplace pas un objet, ni garantit que l'objet est déplaçable. Alors pourquoi s'appelle-t-il bouger?

Je pense que ne pas être mobile peut être l'un des deux scénarios suivants:

1. Le type en mouvement est const.

La raison pour laquelle nous avons le mot-clé const dans le langage est que nous voulons que le compilateur empêche toute modification d'un objet défini comme étant const. À titre d'exemple dans le livre de Scott Meyers:

    class Annotation {
    public:
     explicit Annotation(const std::string text)
     : value(std::move(text)) // "move" text into value; this code
     { … } // doesn't do what it seems to!    
     …
    private:
     std::string value;
    };

Qu'est-ce que cela signifie littéralement? Déplacez une chaîne const vers le membre value - du moins, c'est ce que je comprends avant de lire l'explication.

Si la langue a l'intention de ne pas déplacer ou de ne pas garantir que le mouvement soit applicable lorsque std :: move () est appelé, il est littéralement trompeur lorsque vous utilisez le déplacement de Word.

Si le langage encourage les utilisateurs de std :: move à gagner en efficacité, il doit éviter les pièges de ce type le plus tôt possible, en particulier pour ce type de contradiction littérale évidente.

Je conviens que les gens doivent savoir que déplacer une constante est impossible, mais cette obligation ne doit pas impliquer que le compilateur puisse rester silencieux en cas de contradiction évidente.

2. L'objet n'a pas de constructeur de mouvement

Personnellement, je pense que ceci est une histoire distincte de la préoccupation de OP, comme l'a dit Chris Drew

@ hvd Cela me semble un peu un non-argument. Ce n'est pas parce que la suggestion d'OP ne résout pas tous les bugs du monde que c'est une mauvaise idée (probablement, mais pas pour la raison que vous avez indiquée). - Chris Drew

2
Bogeegee

Je suis surpris que personne n'ait mentionné l'aspect de compatibilité avec cette version. Je crois que std::move a été conçu à cette fin en C++ 11. Imaginez que vous travailliez avec une base de code héritée, qui s'appuie fortement sur les bibliothèques C++ 98. Par conséquent, sans la solution de repli sur l'affectation de copie, le déplacement en résulterait. 

0
mlo