web-dev-qa-db-fra.com

Qu'est-ce que std :: move () et quand doit-il être utilisé?

  1. Qu'Est-ce que c'est?
  2. Qu'est ce que ça fait?
  3. Quand doit-il être utilisé?

Les bons liens sont appréciés.

559
Basilevs

page Wikipedia sur les références de valeur R C++ 11 et les constructeurs de déplacement

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

C'est une nouvelle façon C++ d'éviter les copies. Par exemple, à l'aide d'un constructeur de déplacement, un std::vector pourrait simplement copier son pointeur interne vers les données dans le nouvel objet, laissant l'objet déplacé dans un état incorrect, évitant ainsi de copier toutes les données. Ce serait C++ - valide.

Essayez de chercher Google pour la sémantique du mouvement, la valeur, une transmission parfaite.

236
Scharron

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

Alors que std::move() est techniquement une fonction - je dirais que ce n'est pas vraiment une fonction . C'est en quelque sorte un convertisseur entre les différentes manières 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 déplace réellement rien . Il convertit une expression de lvalue (telle qu'une variable nommée) à xvalue . Une valeur x indique au compilateur:

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

en d'autres termes, lorsque vous utilisez std::move(x), vous autorisez le compilateur à cannibaliser x. Ainsi, si x a, disons, son propre tampon en mémoire - après std::move()ing, le compilateur peut avoir un autre objet le posséder à la place.

Vous pouvez également passer de prvalue (comme un temporaire que vous transmettez), mais cela est rarement utile.

3. "Quand doit-il être utilisé?"

Une autre façon de poser cette question est "Pour quoi devrais-je cannibaliser les ressources d'un objet existant?" Eh bien, si vous écrivez du code d’application, vous ne ferez probablement pas beaucoup de choses avec des objets temporaires créés par le compilateur. Ainsi, vous le feriez principalement dans des endroits tels que les constructeurs, les méthodes d'opérateur, les fonctions standard d'algorithme de bibliothèque, etc., où les objets sont créés et détruits automatiquement. Bien sûr, ce n'est qu'une règle de base.

Une utilisation typique est de "déplacer" des ressources d'un objet à un autre au lieu de les copier. @Guillaume est un lien vers cette page qui contient un exemple simple et simple: échanger deux objets avec moins de copie.

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)
}

utiliser move 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 lorsque T est, par exemple, vector<int> de taille n. Dans la première version, vous lisez et écrivez 3 * n éléments, dans la seconde, vous ne lisez et n'écrivez que les 3 pointeurs vers les tampons des vecteurs. Bien entendu, la classe T doit savoir comment faire le déménagement; votre classe devrait avoir un opérateur d'affectation de mouvement et un constructeur de mouvement pour la classe T pour que cela fonctionne.

174
einpoklum

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 également possible qu'un objet prenne le contenu d'un objet temporaire sans faire de copie (et gagne 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 également un bon lien pour std :: move, et j'ai trouvé les liens ci-dessus un peu "austères".

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

134
Guillaume

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

A: std::move() est une fonction de la bibliothèque standard C++ qui permet de transtyper une référence rvalue.

Simplisticly std::move(t) est équivalent à:

static_cast<T&&>(t);

Une valeur r est une valeur temporaire qui ne persiste pas au-delà de l'expression qui la définit, telle qu'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 constater, std::move renvoie T&& peu importe qu'il soit appelé avec une valeur (T), un type de référence (T&) ou une référence de valeur (T&&) .

Q: Que fait-il?

A: En tant que distribution, il ne fait rien pendant l'exécution. Au moment de la compilation, il est uniquement pertinent de dire au compilateur que vous souhaitez continuer à considérer la référence comme une valeur 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 que cela fait pas :

  • Faire une copie de l'argument
  • Appeler le constructeur de copie
  • Changer l'objet argument

Q: Quand devrait-il être utilisé?

R: Vous devez utiliser std::move si vous souhaitez appeler des fonctions qui prennent en charge la sémantique de déplacement avec un argument qui n'est pas une valeur rvalue (expression temporaire).

Cela pose les questions suivantes pour moi:

  • Qu'est-ce que la sémantique de déplacement? La sémantique de déplacement par opposition à la sémantique de copie est une technique de programmation dans laquelle les membres d'un objet sont initialisés en "prenant la relève" au lieu de copier les membres d'un autre objet. Une telle "prise en charge" n'a de sens que pour les pointeurs et les ressources, qui peuvent être transférés à moindre coût en copiant le pointeur ou la poignée entière plutôt que les données sous-jacentes.

  • Quels types de classes et d'objets supportent la sémantique des mouvements? En tant que développeur, il vous appartient d’implémenter la sémantique des déplacements dans vos propres classes si celles-ci auraient intérêt à transférer leurs membres au lieu de les copier. Une fois que vous avez implémenté la sémantique de déplacement, vous bénéficierez directement du travail de nombreux programmeurs de bibliothèques ayant pris en charge le traitement efficace des classes avec la sémantique de déplacement.

  • Pourquoi le compilateur ne peut-il pas le comprendre par lui-même? Le compilateur ne peut pas simplement appeler une autre surcharge d'une fonction à moins que vous ne le disiez. Vous devez aider le compilateur à choisir si la version normale ou la version mobile de la fonction doit être appelée.

  • Dans quelles situations pourrais-je dire au compilateur qu'il doit traiter une variable comme une valeur? 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é.

52
Christopher Oezbek

std :: move en soi ne fait pas grand chose. Je pensais qu'il appelait le constructeur déplacé pour un objet, mais il s'agissait en réalité d'une transtypage de type (transtyper une variable lvalue en une valeur rvalue afin que ladite variable puisse être transmise en tant qu'argument à un constructeur de déplacement ou à un opérateur d'affectation).

Donc std :: move est simplement utilisé comme un précurseur de l’utilisation de la sémantique du déplacement. La sémantique de déplacement est essentiellement un moyen efficace de traiter des objets temporaires.

Considérons l'objet A = B + C + D + E + F;

C'est un code d'aspect agréable, mais E + F produit un objet temporaire. Ensuite, D + temp produit un autre objet temporaire, etc. 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 quand même supprimés à la fin de la ligne lorsqu'ils sortiront de leur champ d'application.

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 en profondeur se produit est maintenant E + F. Le reste utilise la sémantique de déplacement. Le constructeur de déménagement ou l'opérateur d'affectation doit également être implémenté pour affecter le résultat à A.

30
user929404

"Qu'est-ce que c'est?" et "Qu'est-ce que ça fait?" a été expliqué ci-dessus.

Je vais donner un exemple de "quand il devrait être utilisé".

Par exemple, nous avons une classe avec beaucoup de ressources telles que big array.

class ResHeavy{ //  ResHeavy means heavy resource
    public:
        ResHeavy(int len=10):_upInt(new int[len]),_len(len){
            cout<<"default ctor"<<endl;
        }

        ResHeavy(const ResHeavy& rhs):_upInt(new int[rhs._len]),_len(rhs._len){
            cout<<"copy ctor"<<endl;
        }

        ResHeavy& operator=(const ResHeavy& rhs){
            _upInt.reset(new int[rhs._len]);
            _len = rhs._len;
            cout<<"operator= ctor"<<endl;
        }

        ResHeavy(ResHeavy&& rhs){
            _upInt = std::move(rhs._upInt);
            _len = rhs._len;
            rhs._len = 0;
            cout<<"move ctor"<<endl;
        }

    // check array valid
    bool is_up_valid(){
        return _upInt != nullptr;
    }

    private:
        std::unique_ptr<int[]> _upInt; // heavy array resource
        int _len; // length of int array
};

Code de test:

void test_std_move2(){
    ResHeavy rh; // only one int[]
    // operator rh

    // after some operator of rh, it becomes no-use
    // transform it to other object
    ResHeavy rh2 = std::move(rh); // rh becomes invalid

    // show rh, rh2 it valid
    if(rh.is_up_valid())
        cout<<"rh valid"<<endl;
    else
        cout<<"rh invalid"<<endl;

    if(rh2.is_up_valid())
        cout<<"rh2 valid"<<endl;
    else
        cout<<"rh2 invalid"<<endl;

    // new ResHeavy object, created by copy ctor
    ResHeavy rh3(rh2);  // two copy of int[]

    if(rh3.is_up_valid())
        cout<<"rh3 valid"<<endl;
    else
        cout<<"rh3 invalid"<<endl;
}

sortie comme ci-dessous:

default ctor
move ctor
rh invalid
rh2 valid
copy ctor
rh3 valid

Nous pouvons voir que std::move avec move constructor permet de transformer facilement une ressource.

Où est-ce que std::move est utile?

std::move peut également être utile lors du tri d'un tableau d'éléments. De nombreux algorithmes de tri (tels que le tri par sélection et le tri à bulle) fonctionnent en échangeant des paires d'éléments. Auparavant, nous devions recourir à la sémantique de copie pour effectuer la permutation. Nous pouvons maintenant utiliser la sémantique de déplacement, qui est plus efficace.

Cela peut également être utile si vous souhaitez déplacer le contenu géré par un pointeur intelligent vers un autre.

Cité:

7
Jayhello