6
votes

Pourquoi déplacez-vous la sémantique nécessaire pour élier des copies temporaires?

Donc, ma compréhension de la sémantique de déplacement est qu'ils vous permettent de remplacer les fonctions d'utilisation avec des valeurs temporaires (rapales) et d'éviter des copies potentiellement coûteuses (en déplaçant l'état d'un temporaire sans nom dans votre lvalue nommé).

Ma question C'est pourquoi avons-nous besoin de sémantique spéciale pour cela? Pourquoi un compilateur C ++ n'a-t-il pas pu élider ces copies, puisque c'est le compilateur qui détermine si une expression donnée est une lvalue ou une rvalue? À titre d'exemple: p> xxx pré>

Même sans la sémantique de déplacement de C ++ 11, le compilateur doit toujours pouvoir déterminer que l'expression passée à func () code > est une r devalue, et donc la copie d'un objet temporaire est inutile. Alors pourquoi avoir la distinction du tout? Il semble que cette application de la sémantique de déplacement soit essentiellement une variante de copie d'élision ou d'autres optimisations de compilateur similaires. P>

comme un autre exemple, pourquoi se dérouler comme suit? P>

void func(const std::string& s) {
    // Do something with lvalue string
}

void func(std::string&& s) {
    // Do something with rvalue string
}

int main() {
    std::string s("abc");

    // Presumably calls func(const std::string&) overload
    func(s);

    // Presumably calls func(std::string&&) overload
    func(std::string("abc") + std::string("def"));
}


2 commentaires

Pourriez-vous expliquer comment cela est significativement différent de mon deuxième exemple ci-dessus?


Imaginez que vous êtes le compilateur générant le code de Func ... et il peut y avoir des appels dans d'autres unités de traduction ... Sortez-vous du paramètre ou non?


4 Réponses :


9
votes

Déplacer les fonctions Ne remplissez pas les copies temporaires, exactement.

Le même nombre de temporaires existe, il s'agit simplement d'appeler le constructeur de copie typique, le constructeur de déplacement est appelé, qui est autorisé à cannibaliser l'original plutôt que de faire une copie indépendante. Cela peut parfois être considérablement plus efficace.

Le modèle d'objet formel C ++ n'est pas du tout modifié par déplacer la sémantique. Les objets ont toujours une durée de vie bien définie, à partir d'une adresse particulière et se terminant quand elles sont détruites là-bas. Ils ne "bougent" jamais pendant leur vie. Quand ils sont "déplacés de", ce qui se passe vraiment est que les courants sont gaspillés d'un objet qui doit mourir bientôt et placé efficacement dans un nouvel objet. On peut ressembler à leur déménagement, mais officiellement, ils ne sont pas vraiment, comme cela se briserait totalement C ++.

être déplacé de ce n'est pas la mort. Il est nécessaire de quitter des objets dans un "état valide" dans lequel ils sont toujours vivants et que le destructeur sera toujours appelé plus tard.

Les copies éliminées sont une chose totalement différente, où dans une chaîne d'objets temporaires, certains des intermédiaires sont ignorés. Les compilateurs ne sont pas requis pour éliminer des copies en C ++ 11 et C ++ 14, ils sont autorisés de le faire même lorsqu'il peut violer la règle "As-Si" cela guide généralement l'optimisation. C'est même si la copie CTOR peut avoir des effets secondaires, le compilateur à des réglages d'optimisation élevés peut toujours ignorer certains des temporaires.

En revanche, "Copie garantie" est une nouvelle fonctionnalité C ++ 17, ce qui signifie que la norme nécessite une copie éliminatoire pour avoir lieu dans certains cas.

Déplacez la sémantique et la copie Ellision Donnez deux approches différentes pour permettre une plus grande efficacité dans ces scénarios «chaîne de temporaires». En déménagement sémantique, tous les temporaires existent toujours, mais au lieu d'appeler le constructeur de copie, nous devons appeler un constructeur (espérons-le) moins cher, le constructeur de déplacement. En copier Ellision, nous allons sauter certains des objets ensemble.

Fondamentalement, pourquoi déménager la sémantique est-elle considérée comme spéciale et non seulement une optimisation du compilateur qui aurait pu être effectuée par les compilateurs pré-C ++ 11?

Déplacer la sémantique n'est pas une "optimisation du compilateur". Ils sont une nouvelle partie du système de type. Déplacer la sémantique se produit même lorsque vous compilez avec -O0 sur gcc et clang - il provoque des fonctions différentes pour être Appelé, parce que, le fait qu'un objet est sur le point de mourir est maintenant "annoté" dans le type de référence. Il permet des "optimisations de niveau d'application", mais cela est différent de ce que fait l'optimiseur.

Peut-être que vous pouvez y penser comme un filet de sécurité. Bien sûr, dans un monde idéal, l'optimiseur éliminerait toujours toutes les copies inutiles. Parfois, cependant, la construction d'une temporaire est complexe, implique des allocations dynamiques et le compilateur ne voit pas à travers tout cela. Dans de nombreux cas, vous serez sauvé en déplacez la sémantique, ce qui pourrait vous permettre d'éviter de faire une allocation dynamique du tout. Cela peut à son tour entraîner un code généré qui est alors plus facile pour l'optimiseur d'analyser.

La copie garantie de la copie de la copie est une sorte de commettre, ils ont trouvé un moyen de formaliser une partie de ce "sens commun" sur les temporaires, de sorte que plus de code fonctionne non seulement comme vous vous attendez quand il est optimisé, mais c'est Obligatoire Travailler votre façon d'attendre quand il est compilé et ne pas appeler un constructeur de copie lorsque vous pensez qu'il ne devrait pas y avoir de copie. Donc, vous pouvez par exemple. Renvoie des types non-transférables et non mobiles de la valeur d'une fonction d'usine. Le compilateur tient compte qu'aucune copie n'arrive beaucoup plus tôt dans le processus, avant même d'atteindre l'optimiseur. C'est vraiment la prochaine itération de cette série d'améliorations.


2 commentaires

Ok, je pense que je comprends maintenant. Déplacez la sémantique dans ce cas constitue une «copie plus efficace» qui se déroule néanmoins sur un objet construit temporairement. Serait-il possible pour vous de fournir des exemples de situations dans lesquelles le compilateur serait incapable d'élire ces temporaires? Je penserais dans mon exemple que cela devrait être possible, mais je ne sais pas comment tester à coup sûr.


Sur cette réponse Ce que j'aime le plus: "Bien sûr, dans un monde idéal, l'optimiseur éliminerait toujours toutes les copies inutiles. Parfois, cependant, la construction d'un complexe est complexe, implique des allocations dynamiques et le compilateur ne voit pas tout cela. " Bien que certains exemples soient agréables qui montrent quand il est presque impossible pour le compilateur d'éviter des copies qui déplacent la sémantique peuvent éviter.



1
votes

La réponse est que la sémantique de déplacement a été introduite non pour éliminer des copies. Il a été introduit pour permettre / promouvoir la copie moins chère. Par exemple, si tous les membres de données d'une classe sont des entiers simples, la sémantique de copie sera la même pour déplacer la sémantique. Dans ce cas, il n'a pas de sens de définir le déplacement de la CTOR et de déplacer l'opérateur d'affectation de cette classe. Déplacez la CTOR et déplacer l'affectation a du sens lorsque la classe a quelque chose qui peut être déplacé.

Il y a des tonnes d'articles sur ce sujet. Néanmoins, des notes:

  • Une fois que le paramètre est spécifié sous forme t && il est clair à tout le monde qu'il est ok pour voler son contenu. Point. Simple et clair. En C ++ 03 là n'était pas une syntaxe claire ou une autre convention établie pour transmettre cette idée. En fait, il y a des tonnes d'une autre façon d'exprimer la même chose. Mais le comité a choisi de cette façon.
  • Déplacer sémantique est utile non seulement avec rvalue références. Ça peut être utilisé n'importe où où vous voulez indiquer que vous voulez passer Votre objet à la fonction et cette fonction peut prendre son contenu.

    Vous pouvez avoir ce code: xxx

    Notez que, dans la ligne (1), le déplacement de la CTOR sera utilisé et que l'objet sera ajouté pour le prix moins cher. Notez que cet objet ne meure pas à cette ligne et il n'y a pas de temporarités là-bas.


2 commentaires

Bel exemple qui montre que vous pouvez toujours utiliser objet après son contenu (déplacé). Je ne sais pas si je l'ai vu souvent dans des situations de vie réelles.


La norme est explicite qu'après déplacer CTOR / affectation, l'objet doit être dans l'état correct, de sorte qu'il puisse être détruit en toute sécurité.



3
votes

Copier une élision et une sémantique de déplacement ne sont pas exactement les mêmes. Avec Copy Elision, l'objet entier n'est pas copié, il reste en place. Avec un mouvement, "quelque chose" est toujours copié. La copie n'est pas vraiment éliminée. Mais que "quelque chose" est une ombre pâle de ce qu'une copie pleine soufflée doit transporter.

Un exemple simple: xxx

bonne chance essayant de faire passer votre compilateur Éliminez la copie, ici.

maintenant, ajoutez ce constructeur: xxx

et maintenant, l'objet dans le principal () est construit en utilisant une opération de déplacement. La copie complète n'a pas été éliminée, mais l'opération de déplacement n'est que du bruit de la ligne.

d'autre part: xxx

Ça va obtenir une copie complète ici. Rien n'est copié copié.

En conclusion: Déplacer la sémantique ne s'élige pas réellement ou n'élimine pas une copie. Cela rend simplement la copie résultante "moins".


1 commentaires

Si la classe de barre a un constructeur qui prend const std :: vectorisateur et ensuite compilateur pourrait probablement générer un constructeur de déplacement en prenant automatiquement le vecteur de rvalue pour ce cas lors du passage temporaire. Ceci est probablement compliqué pour le compilateur de faire et non en fonction des normes C ++ actuelles, mais devrait être possible.



2
votes

Vous avez un malentendu fondamental de la manière dont certaines choses en C ++ travaillent:

Même sans la sémantique de déplacement de C ++ 11, le compilateur devrait toujours pouvoir déterminer que l'expression transmise à FUNC () est une r devalue, et donc la copie d'un objet temporaire est inutile.

Ce code ne provoque pas tout copier, même en C ++ 98. Un const & est une référence pas une valeur. Et parce que c'est const , il est parfaitement capable de référencer un temporaire. En tant que tel, une fonction prenant un const string & jamais reçoit une copie du paramètre.

Ce code créera une référence temporaire et transmettra une référence à celle Temporaire à Func . Aucun copie ne se produit, du tout.

comme un autre exemple, pourquoi la peine d'avoir un code comme ce qui suit?

personne ne le fait. Une fonction Ne devrait prendre qu'un paramètre par référence à la référence si cette fonction sera passer de celui-ci . Si une fonction va seulement observer la valeur sans la modifier, ils le prennent par const & , comme en C ++ 98.

le plus important de tous:

Donc, ma compréhension de la sémantique de déplacement, c'est qu'ils vous permettent de remplacer les fonctions d'utilisation avec des valeurs temporaires (rapales) et d'éviter des copies potentiellement coûteuses (en déplaçant l'état d'un temporaire sans nom dans votre lvalue nommé).

Votre compréhension est fausse.

MOVIER n'est pas uniquement sur les valeurs temporaires; Si c'était le cas, nous n'aurions pas std :: déplacer qui nous permet de passer de Lvalues. Le déplacement consiste à transférer la propriété des données d'un objet à un autre. Bien que cela se produise fréquemment avec des temporaires, cela peut également se produire avec des lvalues: xxx

Ce code crée un document_ptr, puis déplace le contenu de ce pointeur dans un autre unique_ptr < / code> objet. Il ne s'agit pas de temporaires; Il transfère la propriété du pointeur interne sur un autre objet.

Ce n'est pas quelque chose qu'un compilateur pourrait déduire que vous vouliez faire. Vous devez être explicite que vous souhaitez effectuer un tel geste sur une lvalue (c'est pourquoi std :: déplacer est là).


1 commentaires

À propos de votre dernier exemple: le compilateur théorique pourrait éventuellement voir que P n'est jamais utilisé à nouveau après avoir été attribué à d'autres_p afin qu'il puisse élier la copie et simplement bouger / réaffecter des pointeurs. Je pense que c'est le point de l'auteur - il pense que presque toutes les caractéristiques (sinon toutes) de la sémantique de déplacement pourraient être effectuées par un compilateur assez intelligent, bien que cela ne soit pas pratiquement réalisable pour certains cas.