796 votes

Qu'est-ce que std::move(), et quand doit-on l'utiliser ?

  1. Qu'est-ce que c'est ?
  2. Que fait-il ?
  3. Quand doit-on l'utiliser ?

Les bons liens sont appréciés.

50 votes

Bjarne Stroustrup explique le déménagement en Une brève introduction aux références Rvalue

2 votes

15 votes

Cette question fait référence à std::move(T && t) ; il existe également une std::move(InputIt first, InputIt last, OutputIt d_first) qui est un algorithme lié à std::copy . Je le signale pour que les autres ne soient pas aussi confus que je l'ai été lorsque j'ai été confronté pour la première fois à un std::move en prenant trois arguments. fr.cppreference.com/w/cpp/algorithme/mouvement

397voto

einpoklum Points 2893

1. "Qu'est-ce que c'est ?"

Alors que std::move() est techniquement une fonction - je dirais ce n'est pas le cas vraiment une fonction . C'est une sorte de convertisseur entre les façons dont le compilateur considère la valeur d'une expression.

2. "Qu'est-ce que ça fait ?"

La première chose à noter est que std::move() ne fait rien bouger . Il fait passer une expression du statut de lvalue (telle qu'une variable nommée) à être une xvalue . Une valeur x indique au compilateur :

Vous pouvez me piller, déplacer tout ce que je détiens et l'utiliser ailleurs (puisque je vais bientôt être détruit de toute façon)".

en d'autres termes, lorsque vous utilisez std::move(x) vous laissez le compilateur cannibaliser x . Ainsi, si x a, disons, sa propre mémoire tampon en mémoire - après que std::move() le compilateur peut faire en sorte qu'un autre objet le possède à la place.

Vous pouvez également passer d'un <a href="http://en.cppreference.com/w/cpp/language/value_category" rel="noreferrer">prvalue </a>(comme un temporaire que vous faites circuler), mais cela est rarement utile.

3. "Quand faut-il l'utiliser ?"

Une autre façon de poser la question est de savoir pourquoi je cannibaliserais les ressources d'un objet existant. Si vous écrivez du code d'application, vous n'aurez probablement pas à vous occuper d'objets temporaires créés par le compilateur. Donc, vous le feriez principalement dans des endroits comme les constructeurs, les méthodes d'opérateurs, les fonctions de type algorithme de bibliothèque standard, etc. où les objets sont souvent créés et détruits automatiquement. Bien sûr, ce n'est qu'une règle empirique.

Une utilisation typique consiste à "déplacer" des ressources d'un objet à un autre au lieu de les copier. @Guillaume renvoie à cette page qui propose un exemple simple et court : échanger deux objets avec moins de copies.

template <class T>
swap(T& a, T& b) {
    T tmp(a);   // we now have two copies of a
    a = b;      // we now have two copies of b (+ discarded a copy of a)
    b = tmp;    // we now have two copies of tmp (+ discarded a copy of b)
}

L'utilisation du déplacement vous permet d'échanger les ressources au lieu de les copier :

template <class T>
swap(T& a, T& b) {
    T tmp(std::move(a));
    a = std::move(b);   
    b = std::move(tmp);
}

Pensez à ce qui se passe quand T est, disons, vector<int> de taille n. Dans la première version, vous lisez et écrivez 3*n éléments, dans la deuxième version, vous lisez et écrivez juste les 3 pointeurs vers les tampons des vecteurs, plus les tailles des 3 tampons. Bien sûr, la classe T doit savoir comment effectuer le déplacement ; votre classe doit comporter un opérateur d'affectation de déplacement et un constructeur de déplacement pour la classe T pour que cela fonctionne.

8 votes

J'ai longtemps entendu parler de ces sémantiques de déplacement, mais je ne les ai jamais examinées. D'après la description que vous avez donnée, il semble qu'il s'agisse d'une copie superficielle et non d'une copie profonde.

19 votes

@TitoneMaurice : Sauf qu'il ne s'agit pas d'une copie - puisque la valeur originale n'est plus utilisable.

5 votes

@Zebrafish vous ne pourriez pas avoir plus tort. Une copie superficielle laisse l'original exactement dans le même état, un déplacement a généralement pour conséquence que l'original est vide ou dans un état autrement valide.

365voto

Scharron Points 5866

Page Wikipedia sur les références aux valeurs R et les constructeurs de déplacement de C++11

  1. En C++11, en plus des constructeurs de copie, les objets peuvent avoir des constructeurs de déplacement.
    (Et en plus des opérateurs d'affectation de copie, ils ont des opérateurs d'affectation de déplacement).
  2. Le constructeur move est utilisé à la place du constructeur copy, si l'objet est de type "rvalue-reference" ( Type && ).
  3. std::move() est un cast qui produit une référence de type rvalue à un objet, pour permettre le déplacement à partir de celui-ci.

C'est une nouvelle façon C++ d'éviter les copies. Par exemple, en utilisant un constructeur move, un objet std::vector pourrait simplement copier son pointeur interne vers les données du nouvel objet, laissant l'objet déplacé dans un état "moved from", et ne copiant donc pas toutes les données. Cette méthode serait conforme à la norme C++.

Essayez de chercher sur Google les termes suivants : move semantics, rvalue, perfect forwarding.

46 votes

La sémantique du déplacement exige que l'objet déplacé reste valide ce qui n'est pas un état incorrect. (Justification : il doit quand même se déstructurer, pour que ça marche).

18 votes

@GMan : bien, il doit être dans un état qui permet de le détruire en toute sécurité, mais, AFAIK, il ne doit pas être utilisable pour autre chose.

9 votes

@ZanLynx : Exact. Notez que la bibliothèque standard exige en plus que les objets déplacés soient assignables, mais ce n'est que pour les objets utilisés dans la stdlib, pas une exigence générale.

155voto

Guillaume Points 520

Vous pouvez utiliser move lorsque vous avez besoin de "transférer" le contenu d'un objet ailleurs, sans faire de copie (c'est-à-dire que le contenu n'est pas dupliqué, c'est pourquoi il peut être utilisé sur certains objets non copiables, comme un unique_ptr). Il est aussi possible pour un objet de prendre le contenu d'un objet temporaire sans faire de copie (et gagner beaucoup de temps), avec std::move.

Ce lien m'a vraiment aidé :

http://thbecker.net/articles/rvalue_references/section_01.html

Je suis désolé si ma réponse arrive trop tard, mais je cherchais aussi un bon lien pour le std::move, et j'ai trouvé les liens ci-dessus un peu "austères".

Il met l'accent sur la référence à la valeur r, dans quel contexte vous devez les utiliser, et je pense que c'est plus détaillé, c'est pourquoi je voulais partager ce lien ici.

27 votes

Bon lien. J'ai toujours trouvé l'article de wikipedia, et d'autres liens sur lesquels je suis tombé par hasard, plutôt déroutants car ils ne font que vous présenter des faits, vous laissant le soin de comprendre la signification/raison d'être réelle. Alors que la "sémantique de déplacement" dans un constructeur est plutôt évidente, tous ces détails sur le passage de valeurs && ne le sont pas... donc la description de type tutoriel était très bien.

98voto

Christopher Oezbek Points 2691

Q : Qu'est-ce que std::move ?

A : std::move() est une fonction de la bibliothèque standard C++ permettant d'effectuer un casting vers une référence rvalue.

En toute simplicité std::move(t) est équivalent à :

static_cast<T&&>(t);

Une rvalue est une valeur temporaire qui ne persiste pas au-delà de l'expression qui la définit, comme un résultat de fonction intermédiaire qui n'est jamais stocké dans une variable.

int a = 3; // 3 is a rvalue, does not exist after expression is evaluated
int b = a; // a is a lvalue, keeps existing after expression is evaluated

Une implémentation pour std::move() est donnée dans N2027 : "Une brève introduction aux références Rvalue". comme suit :

template <class T>
typename remove_reference<T>::type&&
std::move(T&& a)
{
    return a;
}

Comme vous pouvez le voir, std::move renvoie à T&& peu importe s'il est appelé avec une valeur ( T ), le type de référence ( T& ), ou référence rvalue ( T&& ).

Q : Que fait-il ?

R : En tant que fonte, elle ne fait rien pendant l'exécution. Il n'est pertinent qu'au moment de la compilation pour indiquer au compilateur que vous souhaitez continuer à considérer la référence comme une rvalue.

foo(3 * 5); // obviously, you are calling foo with a temporary (rvalue)

int a = 3 * 5;
foo(a);     // how to tell the compiler to treat `a` as an rvalue?
foo(std::move(a)); // will call `foo(int&& a)` rather than `foo(int a)` or `foo(int& a)`

Ce qu'il fait no faire :

  • Faites une copie de l'argument
  • Appelez le constructeur de la copie
  • Modifier l'objet de l'argument

Q : Quand doit-on l'utiliser ?

R : Vous devez utiliser std::move si vous voulez appeler des fonctions qui supportent la sémantique move avec un argument qui n'est pas une rvalue (expression temporaire).

Cela m'amène à poser les questions suivantes :

  • Qu'est-ce que la sémantique du mouvement ? La sémantique du déplacement, par opposition à la sémantique de la copie, est une technique de programmation dans laquelle les membres d'un objet sont initialisés par "reprise" au lieu de copier les membres d'un autre objet. Une telle "reprise" n'a de sens qu'avec les pointeurs et les gestionnaires de ressources, qui peuvent être transférés à moindre coût en copiant le pointeur ou le gestionnaire d'entier plutôt que les données sous-jacentes.

  • Quels types de classes et d'objets supportent la sémantique du déplacement ? C'est à vous, en tant que développeur, d'implémenter la sémantique de déplacement dans vos propres classes si celles-ci ont intérêt à transférer leurs membres au lieu de les copier. Une fois que vous aurez implémenté la sémantique du déplacement, vous bénéficierez directement du travail de nombreux programmeurs de bibliothèques qui ont ajouté un support pour gérer efficacement les classes avec la sémantique du déplacement.

  • Pourquoi le compilateur ne peut-il pas s'en sortir tout seul ? Le compilateur ne peut pas simplement appeler une autre surcharge d'une fonction sans que vous le disiez. Vous devez aider le compilateur à choisir si la version régulière ou mobile de la fonction doit être appelée.

  • Dans quelles situations voudrais-je indiquer au compilateur qu'il doit traiter une variable comme une valeur r ? Cela se produira très probablement dans les fonctions de modèle ou de bibliothèque, où vous savez qu'un résultat intermédiaire pourrait être récupéré (plutôt que d'allouer une nouvelle instance).

5 votes

Gros +1 pour les exemples de code avec la sémantique dans les commentaires. Les autres meilleures réponses définissent std::move en utilisant "move" lui-même - ce qui ne clarifie pas vraiment les choses ! --- Je crois qu'il est utile de mentionner que le fait de ne pas faire une copie de l'argument signifie que la valeur originale ne peut pas être utilisée de manière fiable.

0 votes

Vous devriez ajouter une explication de ce qu'est le sauvetage

37voto

user929404 Points 392

Std::move lui-même ne fait pas grand chose. Je pensais qu'il appelait le constructeur déplacé d'un objet, mais il ne fait qu'effectuer un cast de type (couler une variable lvalue en rvalue pour que ladite variable puisse être passée comme argument à un constructeur move ou un opérateur d'affectation).

Ainsi, std::move est juste utilisé comme un précurseur de l'utilisation de la sémantique de déplacement. La sémantique du déplacement est essentiellement un moyen efficace de traiter les objets temporaires.

Considérer l'objet A = B + (C + (D + (E + F)));

C'est un beau code, mais E + F produit un objet temporaire. Puis D + temp produit un autre objet temporaire et ainsi de suite. Dans chaque opérateur "+" normal d'une classe, des copies profondes se produisent.

Par exemple

Object Object::operator+ (const Object& rhs) {
    Object temp (*this);
    // logic for adding
    return temp;
}

La création de l'objet temporaire dans cette fonction est inutile - ces objets temporaires seront de toute façon supprimés à la fin de la ligne car ils sont hors de portée.

Nous pouvons plutôt utiliser la sémantique de déplacement pour "piller" les objets temporaires et faire quelque chose comme

 Object& Object::operator+ (Object&& rhs) {
     // logic to modify rhs directly
     return rhs;
 }

Cela évite de faire des copies profondes inutiles. En référence à l'exemple, la seule partie où la copie profonde se produit est maintenant E + F. Le reste utilise la sémantique du déplacement. Le constructeur de déplacement ou l'opérateur d'affectation doit également être implémenté pour affecter le résultat à A.

3 votes

Vous avez parlé de la sémantique des déplacements. Vous devriez ajouter à votre réponse comment utiliser std::move car la question le demande.

2 votes

@Koushik std::move ne fait pas grand chose - mais est utilisé pour implémenter la sémantique du déplacement. Si vous ne connaissez pas std::move, vous ne connaissez probablement pas non plus la sémantique du déplacement.

1 votes

"ne fait pas grand chose" (oui juste un static_cast vers une référence rvalue). ce qu'il fait réellement et ce qu'il fait est ce que l'OP a demandé. vous n'avez pas besoin de savoir comment std::move fonctionne mais vous devez savoir ce que fait la sémantique de move. de plus, "mais est utilisé pour implémenter la sémantique de move" c'est l'inverse. connaissez la sémantique de move et vous comprendrez std::move sinon non. move aide juste au mouvement et utilise lui-même la sémantique de move. std::move ne fait rien d'autre que convertir son argument en référence rvalue, ce qui est ce que la sémantique de move requiert.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X