Ma première réponse a été une introduction extrêmement simplifiée pour déplacer la sémantique, et de nombreux détails ont été laissés exprès pour rester simple. Cependant, il y a beaucoup plus à faire pour déplacer la sémantique, et je pensais qu'il était temps pour une deuxième réponse de combler les lacunes. La première réponse est déjà assez ancienne et il ne semblait pas juste de la remplacer simplement par un texte complètement différent. Je pense que cela sert toujours bien de première introduction. Mais si vous voulez approfondir, lisez la suite :)
Stephan T. Lavavej a pris le temps de fournir de précieux commentaires. Merci beaucoup, Stephan!
introduction
La sémantique de déplacement permet à un objet, sous certaines conditions, de s'approprier les ressources externes d'un autre objet. Ceci est important de deux manières:
- Transformer des copies coûteuses en mouvements bon marché. Voir ma première réponse pour un exemple. Notez que si un objet ne gère pas au moins une ressource externe (directement ou indirectement via ses objets membres), la sémantique de déplacement n'offre aucun avantage par rapport à la sémantique de copie. Dans ce cas, copier un objet et déplacer un objet signifie exactement la même chose: - class cannot_benefit_from_move_semantics
{
    int a;        // moving an int means copying an int
    float b;      // moving a float means copying a float
    double c;     // moving a double means copying a double
    char d[64];   // moving a char array means copying a char array
    // ...
};
 
- Implémentation de types sécurisés «de déplacement uniquement»; c'est-à-dire les types pour lesquels la copie n'a pas de sens, mais le déplacement le fait. Les exemples incluent les verrous, les descripteurs de fichiers et les pointeurs intelligents avec une sémantique de propriété unique. Remarque: Cette réponse traite d' - std::auto_ptrun modèle de bibliothèque standard C ++ 98 obsolète, qui a été remplacé par- std::unique_ptrC ++ 11. Les programmeurs C ++ intermédiaires sont probablement au moins assez familiers et- std::auto_ptr, en raison de la "sémantique de déplacement" qu'il affiche, cela semble être un bon point de départ pour discuter de la sémantique de déplacement en C ++ 11. YMMV.
 
Qu'est-ce qu'un déménagement?
La bibliothèque standard C ++ 98 propose un pointeur intelligent avec une sémantique de propriété unique appelée std::auto_ptr<T>. Si vous n'êtes pas familier auto_ptr, son but est de garantir qu'un objet alloué dynamiquement est toujours libéré, même en cas d'exceptions:
{
    std::auto_ptr<Shape> a(new Triangle);
    // ...
    // arbitrary code, could throw exceptions
    // ...
}   // <--- when a goes out of scope, the triangle is deleted automatically
Ce qui est inhabituel, auto_ptrc'est son comportement de "copie":
auto_ptr<Shape> a(new Triangle);
      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        |
        |
  +-----|---+
  |   +-|-+ |
a | p | | | |
  |   +---+ |
  +---------+
auto_ptr<Shape> b(a);
      +---------------+
      | triangle data |
      +---------------+
        ^
        |
        +----------------------+
                               |
  +---------+            +-----|---+
  |   +---+ |            |   +-|-+ |
a | p |   | |          b | p | | | |
  |   +---+ |            |   +---+ |
  +---------+            +---------+
Notez que l'initialisation de bwith ane copie pas le triangle, mais transfère à la place la propriété du triangle de aà b. Nous disons également " aest déplacé vers b " ou "le triangle est déplacé de a vers b ". Cela peut sembler déroutant car le triangle lui-même reste toujours au même endroit dans la mémoire.
  Déplacer un objet signifie transférer la propriété d'une ressource qu'il gère à un autre objet.
Le constructeur de copie de auto_ptrressemble probablement à quelque chose comme ceci (quelque peu simplifié):
auto_ptr(auto_ptr& source)   // note the missing const
{
    p = source.p;
    source.p = 0;   // now the source no longer owns the object
}
Mouvements dangereux et inoffensifs
Ce qui est dangereux, auto_ptrc'est que ce qui ressemble syntaxiquement à une copie est en fait un mouvement. Essayer d'appeler une fonction membre sur un élément déplacé auto_ptrinvoquera un comportement indéfini, vous devez donc être très prudent de ne pas utiliser un élément auto_ptraprès son déplacement:
auto_ptr<Shape> a(new Triangle);   // create triangle
auto_ptr<Shape> b(a);              // move a into b
double area = a->area();           // undefined behavior
Mais ce auto_ptrn'est pas toujours dangereux. Les fonctions d'usine sont un cas d'utilisation parfaitement adapté pour auto_ptr:
auto_ptr<Shape> make_triangle()
{
    return auto_ptr<Shape>(new Triangle);
}
auto_ptr<Shape> c(make_triangle());      // move temporary into c
double area = make_triangle()->area();   // perfectly safe
Notez comment les deux exemples suivent le même modèle syntaxique:
auto_ptr<Shape> variable(expression);
double area = expression->area();
Et pourtant, l'un d'eux invoque un comportement indéfini, tandis que l'autre ne le fait pas. Quelle est donc la différence entre les expressions aet make_triangle()? Ne sont-ils pas tous les deux du même type? En effet, ils le sont, mais ils ont des catégories de valeurs différentes .
Catégories de valeur
De toute évidence, il doit y avoir une différence profonde entre l'expression aqui dénote une auto_ptrvariable et l'expression make_triangle()qui dénote l'appel d'une fonction qui renvoie une auto_ptrvaleur par, créant ainsi un nouvel auto_ptrobjet temporaire à chaque appel. aest un exemple de valeur l , alors que make_triangle()c'est un exemple de valeur r .
Passer de lvalues telles que aest dangereux, car nous pourrions plus tard essayer d'appeler une fonction membre via a, en invoquant un comportement non défini. D'un autre côté, passer de rvalues telles que make_triangle()est parfaitement sûr, car une fois que le constructeur de copie a fait son travail, nous ne pouvons plus utiliser le temporaire. Il n'y a aucune expression qui dénote ledit temporaire; si nous écrivons simplement à make_triangle()nouveau, nous obtenons un autre temporaire. En fait, le temporaire déplacé est déjà parti sur la ligne suivante:
auto_ptr<Shape> c(make_triangle());
                                  ^ the moved-from temporary dies right here
Notez que les lettres let ront une origine historique dans le côté gauche et le côté droit d'une affectation. Ce n'est plus le cas en C ++, car il y a des valeurs qui ne peuvent pas apparaître sur le côté gauche d'une affectation (comme des tableaux ou des types définis par l'utilisateur sans opérateur d'affectation), et il y a des rvalues qui peuvent (toutes les rvalues des types de classe avec un opérateur d'affectation).
  Une valeur r de type classe est une expression dont l'évaluation crée un objet temporaire. Dans des circonstances normales, aucune autre expression à l'intérieur de la même portée ne dénote le même objet temporaire.
Références de valeur
Nous comprenons maintenant que passer de lvalues est potentiellement dangereux, mais passer de rvalues est inoffensif. Si C ++ avait un support de langage pour distinguer les arguments lvalue des arguments rvalue, nous pourrions soit interdire complètement le déplacement à partir de lvalues, soit au moins rendre explicite le déplacement à partir de lvalues sur le site d'appel, afin de ne plus bouger par accident.
La réponse de C ++ 11 à ce problème est les références rvalue . Une référence rvalue est un nouveau type de référence qui se lie uniquement aux rvalues, et la syntaxe est X&&. La bonne vieille référence X&est maintenant connue sous le nom de référence lvalue . (Notez que ce X&&n'est pas une référence à une référence; il n'y a rien de tel en C ++.)
Si nous jetons constdans le mélange, nous avons déjà quatre types de références différents. À quels types d'expressions de type Xpeuvent-ils se lier?
            lvalue   const lvalue   rvalue   const rvalue
---------------------------------------------------------              
X&          yes
const X&    yes      yes            yes      yes
X&&                                 yes
const X&&                           yes      yes
En pratique, vous pouvez oublier const X&&. Restreindre à lire à partir des valeurs r n'est pas très utile.
  Une référence rvalue X&&est un nouveau type de référence qui se lie uniquement aux rvalues.
Conversions implicites
Les références Rvalue sont passées par plusieurs versions. Depuis la version 2.1, une référence rvalue X&&se lie également à toutes les catégories de valeurs d'un type différent Y, à condition qu'il y ait une conversion implicite de Yà X. Dans ce cas, un temporaire de type Xest créé et la référence rvalue est liée à ce temporaire:
void some_function(std::string&& r);
some_function("hello world");
Dans l'exemple ci-dessus, "hello world"est une valeur l de type const char[12]. Puisqu'il y a une conversion implicite de const char[12]à const char*en std::string, un temporaire de type std::stringest créé et rest lié à ce temporaire. C'est l'un des cas où la distinction entre rvalues (expressions) et temporelles (objets) est un peu floue.
Déplacer les constructeurs
Un exemple utile de fonction avec un X&¶mètre est le constructeur de déplacement X::X(X&& source) . Son objectif est de transférer la propriété de la ressource gérée de la source à l'objet actuel.
En C ++ 11, std::auto_ptr<T>a été remplacé par std::unique_ptr<T>qui tire parti des références rvalue. Je développerai et discuterai d'une version simplifiée de unique_ptr. Tout d'abord, nous encapsulons un pointeur brut et surchargeons les opérateurs ->et *, par conséquent, notre classe se sent comme un pointeur:
template<typename T>
class unique_ptr
{
    T* ptr;
public:
    T* operator->() const
    {
        return ptr;
    }
    T& operator*() const
    {
        return *ptr;
    }
Le constructeur prend possession de l'objet et le destructeur le supprime:
    explicit unique_ptr(T* p = nullptr)
    {
        ptr = p;
    }
    ~unique_ptr()
    {
        delete ptr;
    }
Vient maintenant la partie intéressante, le constructeur de mouvement:
    unique_ptr(unique_ptr&& source)   // note the rvalue reference
    {
        ptr = source.ptr;
        source.ptr = nullptr;
    }
Ce constructeur de déplacement fait exactement ce que le auto_ptrconstructeur de copie a fait, mais il ne peut être fourni qu'avec des rvalues:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);                 // error
unique_ptr<Shape> c(make_triangle());   // okay
La deuxième ligne ne parvient pas à compiler, car il as'agit d'une valeur l, mais le paramètre unique_ptr&& sourcene peut être lié qu'à des valeurs r. C'est exactement ce que nous voulions; les mouvements dangereux ne devraient jamais être implicites. La troisième ligne se compile très bien, car il make_triangle()s'agit d'une valeur r. Le constructeur du déménagement transférera la propriété du temporaire au c. Encore une fois, c'est exactement ce que nous voulions.
  Le constructeur de déplacement transfère la propriété d'une ressource gérée dans l'objet actuel.
Déplacer les opérateurs d'affectation
La dernière pièce manquante est l'opérateur d'affectation de déplacement. Son travail consiste à libérer l'ancienne ressource et à acquérir la nouvelle ressource à partir de son argument:
    unique_ptr& operator=(unique_ptr&& source)   // note the rvalue reference
    {
        if (this != &source)    // beware of self-assignment
        {
            delete ptr;         // release the old resource
            ptr = source.ptr;   // acquire the new resource
            source.ptr = nullptr;
        }
        return *this;
    }
};
Notez comment cette implémentation de l'opérateur d'affectation de déplacement duplique la logique du destructeur et du constructeur de déplacement. Connaissez-vous l'idiome de copie et d'échange? Il peut également être appliqué pour déplacer la sémantique en tant qu'idiome de déplacement et d'échange:
    unique_ptr& operator=(unique_ptr source)   // note the missing reference
    {
        std::swap(ptr, source.ptr);
        return *this;
    }
};
Maintenant que sourcec'est une variable de type unique_ptr, elle sera initialisée par le constructeur de déplacement; c'est-à-dire que l'argument sera déplacé dans le paramètre. L'argument doit toujours être une valeur r, car le constructeur de déplacement lui-même a un paramètre de référence rvalue. Lorsque le flux de contrôle atteint l'accolade de fermeture de operator=, sourcesort du champ d'application, libérant automatiquement l'ancienne ressource.
  L'opérateur d'affectation de déplacement transfère la propriété d'une ressource gérée dans l'objet actuel, libérant l'ancienne ressource. L'idiome move-and-swap simplifie l'implémentation.
Passer de lvalues
Parfois, nous voulons passer de lvalues. Autrement dit, nous voulons parfois que le compilateur traite une valeur l comme s'il s'agissait d'une valeur r, de sorte qu'il peut invoquer le constructeur de déplacement, même s'il peut être potentiellement dangereux. À cette fin, C ++ 11 propose un modèle de fonction de bibliothèque standard appelé std::moveà l'intérieur de l'en-tête <utility>. Ce nom est un peu malheureux, car std::moveil jette simplement une valeur l à une valeur r; il ne pas bouger quoi que ce soit par lui - même. Il permet simplement de bouger. Peut-être qu'il aurait dû être nommé std::cast_to_rvalueou std::enable_move, mais nous sommes coincés avec le nom maintenant.
Voici comment vous passez explicitement d'une valeur l:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a);              // still an error
unique_ptr<Shape> c(std::move(a));   // okay
Notez qu'après la troisième ligne, ane possède plus de triangle. Ce n'est pas grave, car en écrivant explicitementstd::move(a) , nous avons clairement exprimé nos intentions: "Cher constructeur, faites tout ce que vous voulez apour initialiser c; je m'en fiche aplus. N'hésitez pas à vous débrouiller a."
  std::move(some_lvalue) convertit une lvalue en une rvalue, permettant ainsi un déplacement ultérieur.
Xvalues
Notez que même s'il std::move(a)s'agit d'une valeur r, son évaluation ne crée pas d'objet temporaire. Cette énigme a forcé le comité à introduire une troisième catégorie de valeur. Quelque chose qui peut être lié à une référence rvalue, même s'il ne s'agit pas d'une valeur r au sens traditionnel, est appelé une valeur x (valeur eXpiring). Les valeurs traditionnelles ont été renommées en valeurs (valeurs pures).
Les valeurs pr et x sont des valeurs r. Les valeurs X et les valeurs l sont toutes deux des valeurs gl (valeurs l généralisées). Les relations sont plus faciles à saisir avec un diagramme:
        expressions
          /     \
         /       \
        /         \
    glvalues   rvalues
      /  \       /  \
     /    \     /    \
    /      \   /      \
lvalues   xvalues   prvalues
Notez que seules les valeurs x sont vraiment nouvelles; le reste est simplement dû au changement de nom et au regroupement.
  C ++ 98 rvalues sont appelées prvalues dans C ++ 11. Remplacez mentalement toutes les occurrences de "rvalue" dans les paragraphes précédents par "prvalue".
Sortir des fonctions
Jusqu'à présent, nous avons vu des mouvements dans des variables locales et dans des paramètres de fonction. Mais le déplacement est également possible en sens inverse. Si une fonction renvoie par valeur, un objet sur le site d'appel (probablement une variable locale ou temporaire, mais pourrait être n'importe quel type d'objet) est initialisé avec l'expression après l' returninstruction comme argument pour le constructeur de déplacement:
unique_ptr<Shape> make_triangle()
{
    return unique_ptr<Shape>(new Triangle);
}          \-----------------------------/
                  |
                  | temporary is moved into c
                  |
                  v
unique_ptr<Shape> c(make_triangle());
De manière surprenante, les objets automatiques (variables locales qui ne sont pas déclarées comme static) peuvent également être implicitement retirés des fonctions:
unique_ptr<Shape> make_square()
{
    unique_ptr<Shape> result(new Square);
    return result;   // note the missing std::move
}
Comment se fait-il que le constructeur de mouvement accepte la valeur l resultcomme argument? La portée de resultest sur le point de se terminer et elle sera détruite lors du déroulement de la pile. Personne ne pouvait se plaindre par la suite qui resultavait changé d'une manière ou d'une autre; lorsque le flux de contrôle est de retour à l'appelant, resultn'existe plus! Pour cette raison, C ++ 11 a une règle spéciale qui permet de retourner des objets automatiques à partir de fonctions sans avoir à écrire std::move. En fait, vous ne devez jamais utiliser std::movepour déplacer des objets automatiques hors des fonctions, car cela inhibe l '"optimisation de la valeur de retour nommée" (NRVO).
  Ne jamais utiliser std::movepour déplacer des objets automatiques hors des fonctions.
Notez que dans les deux fonctions d'usine, le type de retour est une valeur, pas une référence rvalue. Les références Rvalue sont toujours des références, et comme toujours, vous ne devez jamais renvoyer une référence à un objet automatique; l'appelant se retrouverait avec une référence pendant si vous incitiez le compilateur à accepter votre code, comme ceci:
unique_ptr<Shape>&& flawed_attempt()   // DO NOT DO THIS!
{
    unique_ptr<Shape> very_bad_idea(new Square);
    return std::move(very_bad_idea);   // WRONG!
}
  Ne renvoyez jamais d'objets automatiques par référence rvalue. Le déplacement est exclusivement effectué par le constructeur de déplacement, pas par std::moveet pas simplement en liant une rvalue à une référence rvalue.
Entrer dans les membres
Tôt ou tard, vous allez écrire du code comme ceci:
class Foo
{
    unique_ptr<Shape> member;
public:
    Foo(unique_ptr<Shape>&& parameter)
    : member(parameter)   // error
    {}
};
Fondamentalement, le compilateur se plaindra qu'il parameters'agit d'une valeur l. Si vous regardez son type, vous voyez une référence rvalue, mais une référence rvalue signifie simplement "une référence qui est liée à une rvalue"; cela ne signifie pas que la référence elle-même est une valeur! En effet, parameterc'est juste une variable ordinaire avec un nom. Vous pouvez utiliser parameterautant de fois que vous le souhaitez à l'intérieur du corps du constructeur, et il désigne toujours le même objet. S'en éloigner implicitement serait dangereux, d'où la langue l'interdit.
  Une référence rvalue nommée est une lvalue, comme toute autre variable.
La solution consiste à activer manuellement le déplacement:
class Foo
{
    unique_ptr<Shape> member;
public:
    Foo(unique_ptr<Shape>&& parameter)
    : member(std::move(parameter))   // note the std::move
    {}
};
On pourrait dire que ce parametern'est plus utilisé après l'initialisation de member. Pourquoi n'y a-t-il pas de règle spéciale à insérer silencieusement std::movecomme pour les valeurs de retour? Probablement parce que ce serait trop lourd pour les implémenteurs du compilateur. Par exemple, que se passe-t-il si le corps du constructeur se trouve dans une autre unité de traduction? En revanche, la règle de valeur de retour doit simplement vérifier les tables de symboles pour déterminer si l'identifiant après le returnmot - clé désigne un objet automatique.
Vous pouvez également transmettre la parametervaleur par. Pour les types à déplacement uniquement comme unique_ptr, il semble qu'il n'y ait pas encore d'idiome établi. Personnellement, je préfère passer par valeur, car cela provoque moins d'encombrement dans l'interface.
Fonctions spéciales des membres
C ++ 98 déclare implicitement trois fonctions membres spéciales à la demande, c'est-à-dire lorsqu'elles sont nécessaires quelque part: le constructeur de copie, l'opérateur d'affectation de copie et le destructeur.
X::X(const X&);              // copy constructor
X& X::operator=(const X&);   // copy assignment operator
X::~X();                     // destructor
Les références Rvalue sont passées par plusieurs versions. Depuis la version 3.0, C ++ 11 déclare à la demande deux fonctions membres spéciales supplémentaires: le constructeur de déplacement et l'opérateur d'affectation de déplacement. Notez que ni VC10 ni VC11 ne sont encore conformes à la version 3.0, vous devrez donc les implémenter vous-même.
X::X(X&&);                   // move constructor
X& X::operator=(X&&);        // move assignment operator
Ces deux nouvelles fonctions membres spéciaux ne sont déclarées implicitement que si aucune des fonctions membres spéciales n'est déclarée manuellement. De plus, si vous déclarez votre propre constructeur de déplacement ou opérateur d'affectation de déplacement, ni le constructeur de copie ni l'opérateur d'affectation de copie ne seront déclarés implicitement.
Que signifient ces règles dans la pratique?
  Si vous écrivez une classe sans ressources non gérées, il n'est pas nécessaire de déclarer vous-même l'une des cinq fonctions membres spéciales, et vous obtiendrez une copie sémantique correcte et déplacerez la sémantique gratuitement. Sinon, vous devrez implémenter vous-même les fonctions spéciales des membres. Bien sûr, si votre classe ne bénéficie pas de la sémantique des mouvements, il n'est pas nécessaire d'implémenter les opérations de déplacement spéciales.
Notez que l'opérateur d'affectation de copie et l'opérateur d'affectation de déplacement peuvent être fusionnés en un seul opérateur d'affectation unifié, en prenant son argument par valeur:
X& X::operator=(X source)    // unified assignment operator
{
    swap(source);            // see my first answer for an explanation
    return *this;
}
De cette façon, le nombre de fonctions membres spéciales à implémenter passe de cinq à quatre. Il y a un compromis à faire entre sécurité d'exception et efficacité ici, mais je ne suis pas un expert en la matière.
Renvoi de références ( précédemment appelées références universelles )
Considérez le modèle de fonction suivant:
template<typename T>
void foo(T&&);
Vous pouvez vous attendre T&&à ne vous lier qu'à rvalues, car à première vue, cela ressemble à une référence rvalue. Comme il s'avère cependant, T&&se lie également aux valeurs l:
foo(make_triangle());   // T is unique_ptr<Shape>, T&& is unique_ptr<Shape>&&
unique_ptr<Shape> a(new Triangle);
foo(a);                 // T is unique_ptr<Shape>&, T&& is unique_ptr<Shape>&
Si l'argument est une valeur r de type X, Tse déduit être X, donc T&&moyen X&&. C'est ce à quoi tout le monde s'attendrait. Mais si l'argument est une lvalue de type X, en raison d'une règle spéciale, Ton en déduit être X&, par conséquent T&&signifierait quelque chose comme X& &&. Mais puisque C ++ n'a toujours aucune notion de références aux références, le type X& &&est réduit en X&. Cela peut sembler déroutant et inutile au début, mais l'effondrement des références est essentiel pour une transmission parfaite (qui ne sera pas discutée ici).
  T&& n'est pas une référence de valeur, mais une référence de transfert. Il se lie également aux valeurs l, auquel cas Tet T&&sont toutes deux des références lvalue.
Si vous souhaitez contraindre un modèle de fonction à rvalues, vous pouvez combiner SFINAE avec des traits de type:
#include <type_traits>
template<typename T>
typename std::enable_if<std::is_rvalue_reference<T&&>::value, void>::type
foo(T&&);
Mise en œuvre du déménagement
Maintenant que vous comprenez l'effondrement des références, voici comment std::moveest implémenté:
template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}
Comme vous pouvez le voir, moveaccepte tout type de paramètre grâce à la référence de transfert T&&et renvoie une référence rvalue. L' std::remove_reference<T>::typeappel de la méta-fonction est nécessaire car sinon, pour les valeurs de type X, le type de retour serait X& &&, ce qui s'effondrerait X&. Étant donné qu'il ts'agit toujours d'une valeur l (rappelez-vous qu'une référence rvalue nommée est une valeur l), mais que nous voulons nous lier tà une référence rvalue, nous devons explicitement convertir tle type de retour correct. L'appel d'une fonction qui renvoie une référence rvalue est lui-même une valeur x. Vous savez maintenant d'où viennent les valeurs x;)
  L'appel d'une fonction qui renvoie une référence rvalue, telle que std::move, est une valeur x.
Notez que le retour par référence rvalue est correct dans cet exemple, car tne dénote pas un objet automatique, mais plutôt un objet qui a été transmis par l'appelant.