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

113

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

struct Cat {
   Cat(){}
};

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

quand j'appelle std::move, cela signifie que je veux déplacer l'objet, c'est-à-dire que je vais changer l'objet. Déplacer un constobjet n'est pas raisonnable, alors pourquoi ne std::moverestreint-il pas ce comportement? Ce sera un piège dans le futur, non?

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

"Je pense qu'il veut dire qu'il" le piège "sournoisement parce que s'il ne réalise pas, il finit 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;
};

S'il std::moveétait interdit d'opérer sur un constobjet, nous pourrions facilement trouver le bogue, non?

camino
la source
2
Mais essayez de le déplacer. Essayez de changer son état. std::movepar lui-même ne fait rien à l'objet. On pourrait dire qu'il std::moveest mal nommé.
juanchopanza
3
Cela ne bouge en fait rien. Tout ce qu'il fait, c'est convertir une référence rvalue. essayez CAT cat2 = std::move(cat);, en supposant qu'il CATprend en charge l'assignation de déplacement régulière.
WhozCraig
11
std::moveest juste un casting, il ne bouge en fait rien
Alerte rouge
2
@WhozCraig: Attention, car le code que vous avez publié se compile et s'exécute sans avertissement, ce qui le rend en quelque sorte trompeur.
Mooing Duck
1
@MooingDuck n'a jamais dit qu'il ne compilerait pas. cela ne fonctionne que parce que le copy-ctor par défaut est activé. Squelch ça et les roues tombent.
WhozCraig

Réponses:

55
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);

ici, nous voyons une utilisation de std::movesur un T const. Il renvoie un T const&&. Nous avons un constructeur de mouvement pour strangequi prend exactement ce type.

Et ça s'appelle.

Maintenant, il est vrai que ce type étrange est plus rare que les bogues que votre proposition résoudrait.

Mais, d'un autre côté, l'existant std::movefonctionne mieux dans le code générique, où vous ne savez pas si le type avec lequel vous travaillez est a Tou a T const.

Yakk - Adam Nevraumont
la source
3
+1 pour être la première réponse qui en fait des tentatives pour expliquer pourquoi vous voulez appeler std::movesur un constobjet.
Chris Drew
+1 pour montrer une prise de fonction const T&&. Cela exprime un "protocole API" du genre "Je prendrai un rvalue-ref mais je promets que je ne le modifierai pas". Je suppose que, mis à part lors de l'utilisation de mutable, c'est rare. Peut-être qu'un autre cas d'utilisation est de pouvoir l'utiliser forward_as_tuplesur presque tout et de l'utiliser plus tard.
F Pereira
107

Il y a un truc ici que vous oubliez, à savoir qui std::move(cat) ne bouge en fait rien . Il dit simplement au compilateur d' essayer de se déplacer. Cependant, comme votre classe n'a pas de constructeur qui accepte a const CAT&&, elle utilisera à la place le const CAT&constructeur de copie implicite et copiera en toute sécurité. Aucun danger, aucun piège. Si le constructeur de copie est désactivé pour une raison quelconque, vous obtiendrez une erreur de compilation.

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

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

imprime COPY, non MOVE.

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

Notez que le bogue dans le code que vous mentionnez est un problème de performances , pas un problème de stabilité , donc un tel bogue ne provoquera jamais de plantage. Il utilisera simplement une copie plus lente. De plus, un tel bogue se produit également pour les objets non const qui n'ont pas de constructeur de déplacement, donc simplement ajouter une constsurcharge ne les attrapera pas tous. Nous pourrions vérifier la possibilité de déplacer la construction ou de déplacer l'assignation du type de paramètre, mais cela interférerait avec le code de modèle générique qui est censé se rabattre sur le constructeur de copie. Et diable, peut-être que quelqu'un veut pouvoir construire à partir de const CAT&&qui suis-je pour dire qu'il ne peut pas?

Canard meunier
la source
Upticked. Il convient de noter que la suppression implicite du copy-ctor lors de la définition définie par l'utilisateur du constructeur de mouvement régulier ou de l'opérateur d'affectation le démontrera également via une compilation interrompue. Bonne réponse.
WhozCraig
Il convient également de mentionner qu'un constructeur de copie qui a besoin d'une valeur non-l constn'aidera pas non plus. [class.copy] §8: "Sinon, le constructeur de copie implicitement déclaré aura la forme X::X(X&)"
Deduplicator
9
Je ne pense pas qu'il voulait dire «piège» en termes d'ordinateur / d'assemblage. Je pense qu'il veut dire qu'il le "piège" sournoisement parce que s'il ne se rend pas compte, il finit avec une copie qui n'est pas ce qu'il voulait. Je suppose ..
Brandon
Puissiez-vous obtenir 1 vote de plus, et j'en ai 4 de plus, pour un badge en or. ;)
Yakk - Adam Nevraumont
Haut cinq sur cet exemple de code. Comprend très bien le point.
Brent écrit le code le
20

L'une des raisons pour lesquelles le reste des réponses a négligé jusqu'à présent est la capacité du code générique à être résilient face au mouvement. 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, maintenant je peux créer relativement efficacement un à vector<string>partir d'un deque<string>et chaque individu stringsera déplacé dans le processus.

Mais que faire si je veux passer d'un 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 on std::moveinsiste sur un non- constargument, l'instanciation de ci-dessus move_eachne se compilera pas car elle essaie de déplacer a const int(the key_typeof the map). Mais ce code ne se soucie pas s'il ne peut pas déplacer le fichier 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, qui std::moveest une demande de déplacement , pas une demande de déplacement.

Howard Hinnant
la source
2

J'ai la même inquiétude que l'OP.

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

Je pense qu'être non mobile peut être l'un des deux scénarios suivants:

1. Le type de déplacement 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. Prenant l'exemple du 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 de valeur - du moins, c'est ce que je comprends avant de lire l'explication.

Si le langage a l'intention de ne pas faire de mouvement ou de ne pas garantir que move est applicable lorsque std :: move () est appelé, alors il est littéralement trompeur lors de l'utilisation de word move.

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

Je conviens que les gens doivent être conscients que déplacer une constante est impossible, mais cette obligation ne doit pas impliquer que le compilateur peut se taire lorsqu'une contradiction évidente se produit.

2. L'objet n'a pas de constructeur de déplacement

Personnellement, je pense que c'est une histoire distincte de la préoccupation d'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 corrige pas tous les bogues dans le monde que c'est une mauvaise idée (c'est probablement le cas, mais pas pour la raison que vous donnez). - Chris Drew

Bogeegee
la source
1

Je suis surpris que personne n'ait mentionné l'aspect de compatibilité descendante de cela. Je crois, a std::moveété conçu à dessein pour faire cela en C ++ 11. Imaginez que vous travaillez avec une base de code héritée, qui repose fortement sur les bibliothèques C ++ 98, donc sans le repli sur l'affectation de copie, le déplacement casserait les choses.

mlo
la source