Une nouvelle fonctionnalité de C++11:la sémantique de mouvement

Ce tutoriel est une brève introduction à la sémantique de mouvement en C++.

a marqué ce sujet comme résolu.

Bonjour à tous,

J'ai commencé la rédaction d'un tutoriel dont l'intitulé est Une nouvelle fonctionnalité de C++11 : la sémantique de mouvement.

J'aimerais que ceux qui ne connaissent pas les notions abordées dans le tutoriel me fassent un retour sur les passages qu'ils trouvent peu clairs, et que les autres me préviennent en cas d'oubli, d'imprécision ou d'erreur. J'apprécierai aussi que ceux-ci me donnent des idées d'exemples pour illustrer les notions abordes, que j'ajouterai dans la section Exemples.

Si vous êtes intéressé, cliquez ci-dessous

Merci d'avance pour votre aide !

+1 -0

Salut, j'ai quelques petites remarques

a- Pour swap, habituellement (même si ça ne changera rien dans cet exemple précis), on va faire un using std::swap; puis appeler swap(this->m_toto, other_.m_toto); directement. On profite du Koening Namespace Lookup/ADL pour appeler le bon swap.

b- Intéressant std::exchange, j'avais zappé son apparition

c- Pour le move-and-swap, je ne saurais trop dire s'il est pertinent. Il me semble avoir croisé des critiques assez fortes sur GOTW, mais Meyers qui n'excluait pas son utilisation.

d- "Petite astuce pour les feignants". Pour bien faire, il manquerait la même chose pour la destructeur. C'est dans la continuité de la règle du "tout ou rien" qui est un raffinement de la règle du 0/5. http://arne-mertz.de/2015/02/the-rule-of-zero-revisited-the-rule-of-all-or-nothing/ (Meyers semble avoir validé cette évolution dans les commentaires de son billet où il proposait lui-même un patch sur la règle du 0/5).

e- Quand j'ai étudié la sémantique de déplacement, je me suis heurté à quelques évidences (après réflexion) qui sur le coup n'en étaient pas.

  • T && ref = std::move(variable); n'implique aucun déplacement. On ne fait que créer une référence. Le déplacement sera une conséquence d'un appel à un constructeur de déplacement ou un opérateur de déplacement par affectation, ou d'une fonction qui va procéder à des choses équivalentes (genre push_back ou emplace_back). Et c'est à cause de ce détail technique que Sutter et Meyers sont en discordes sur les signatures de fonctions puits (cf mon point h-)

f- Il est important de rappeler qu'après déplacement un objet volé doit offrir une garantie basique: il doit être destructible. Voire affectable. Après on peut supposer qu'il est une erreur de programmation de vouloir faire plus avec (un des articles sur l'évolution du langage sous-entendait ça – une des conséquences étant que le test &other == this était superflu sur un opérateur de déplacement).

g- Les déplacements seront permis sur le vecteur si les types déplaçables le sont avec une garantie de noexcept.

h- Il pourrait être intéressant de donner la signature des fonctions puits qui reçoivent des objets, mais des gurus comme Sutter et Meyers ne semblent pas d'accord sur la signature idiomatique dans le "simple" cas des objets uniquement déplaçables…

Salut, merci pour tes remarques très intéressantes !

a- Ok, je l'ajouterai

c- Je laisserai le code tel quel, mais je préciserai que cette approche peut être critiquée

d- Très intéressant, je note !

e- Je crois que j'ai bien précisé que std::move ne faisait aucun mouvement.

f- C'est vrai que je ne l'ai pas dit explicitement, je vais le faire.

g- Oui, il était prévu que je l'ajoute (en introduisant move_if_noexcept, comme me l'a suggéeré jo_link_noir)

h- C'est quoi une fonction puits ? :euh:

En tout cas, merci encore, je manquais de lecteurs expérimentés ;)

+0 -0

e- Le problème n'est pas tant que move ne déplace rien, mais que T&& ne déplace rien. En effet: T&& v = a+b; est sans move et pourtant rien n'est déplacé non plus.

C'est là qu'est une source du soucis avec les fonctions puits.

h- Une fonction puits est une fonction qui s'empare des paramètres pour lesquels elle agit comme un puits. Et la question est: void sink(T&&); ou void sink(T); ? Dans le premier cas, sémantiquement, il apparait évident à S.Meyers que la fonction est un puits, mais pas à H.Sutter et d'autres qui voient juste une fonction qui prend une référence (sur une rvalue). Alors que dans le second cas, une valeur d'un type uniquement déplaçable sera obligatoirement déplacée (avec un déplacement de plus, chose que S.Meyers critique). Bref. http://scottmeyers.blogspot.fr/2014/07/should-move-only-types-ever-be-passed.html (ou encore: http://www.reddit.com/r/cpp/comments/2blz6h/scott_meyers_should_moveonly_types_ever_be_passed/cj7d3a3)

A l'opposé, il y a les fonctions sources. Là, heureusement il n'y a aucun débat : il faut renvoyer par valeur et faire un return sans move. Sinon, on risque de se priver de la RVO.

Disons que c'est difficile de faire des garanties d'exception fortes sans && (enfin il me semble qu'on perd l'état de la variable initiale quoi qu'il arrive, difficile de la remettre dans son ancien état en cas d'exception). De même lorsque le déplacement est conditionnel (car dépendant d'une variable dynamique).

+1 -0
  1. Tes définitions de lvalue/rvalue sont à revoir. Par exemple une xvalue désigne bien un objet mais n'est pas une lvalue. Je ne pense pas qu'il soit nécessaire de bien les définir, mais dans ce cas il ne faut pas les présenter comme telles.

  2. "Le compilateur sait très bien faire les optimisations incluant l'utilisation du mouvement ! " Décrire l'utilisation automatique de la sémantique de déplacement par le compilateur comme une optimisation me gène. Le compilateur utilise la sémantique de déplacement parce qu'il le doit selon la norme, ce ne sont pas des cas d'optimisation (comme l'est l'élision par exemple).

  3. "Tout cela est utilisé par la fonction std::forward, qui permet de "choisir" la bonne référence selon si ce qu'on lui donne en paramètre est une lvalue ou une rvalue. […] Ainsi, si elle est appelée sur une lvalue de type A, le paramètre sera évalué comme A&, et on obtient :" Non, la bonne version de forward est choisie parce que tu donnes explicitement le paramètre template, rien à voir avec la nature du paramètre.

  4. Ton tableau avec les 4 cas n'a pas grand chose à voir avec ce qui se produit avec la sémantique de déplacement, le seul cas qui se produit parmi ces 4 est T & &&. La sémantique de déplacement marche uniquement parce que la règle de déduction des paramètres template est spécifique pour T&&.

Tout d'abord, merci pour tes commentaires :)

  1. Je verrais comment améliorer tout ça :) Je voulais rester simple, mais je vais essayer d'être plus exact.
  2. Je préciserai que c'est dans la norme, mais cela reste tout de même dans le but d'optimiser.
  3. Je crois ne pas t'avoir bien compris. En quoi nos propos se contredisent ?
  4. Mon tableau est dans la section bonus, avec une utilisation des rvalue references qui n'est pas directement liée au mouvement. Donc c'est normal qu'il n'ait rien à voir avec ce qui se produit avec la sémantique de déplacement, puisque ce n'est pas pour cela que je l'ai mis ;)
+0 -0
  1. Oui, mais un peu trop simple, le contre exemple que je te donne est typiquement ce qui se passe lors de l'utilisation de la sémantique de mouvement. Un variable temporaire désigne tout autant un objet qu'une variable non temporaire.

  2. Pas certain, la naissance de la sémantique de mouvement c'est plus probablement une volonté d'avoir des fonctions pouvant travailler sur des temporaires sans être limité par la constance et/ou pour corriger les problématiques expérimentées avec unique_ptr. J'ai peur qu'en la décrivant comme une optimisation ça donne l'idée que ce n'est qu un truc en plus et rien d'autre.

  3. Quand tu passes une expression à forward, peu importe que ce soit une rvalue ou une lvalue, ça ne change rien au type du résultat qui ne dépend que de la nature de ce qu'il y a entre <>

  4. Pas grand chose à voir avec le perfect forwarding non plus.

+1 -0

Après vampirisation, l'objet sera dans un état cohérent. Encore heureux. C'est la Garantie Basique, sans ça, le programme serait buggué. La non-cohérence est une erreur de programmation. A la limite elle ne pourrait pas l'être s'il y avait un flag quelque part pour dire : non cohérent. Mais, l'ajout de ce flag participerait de fait à la définition de ce qu'est être cohérent pour l'objet étudié. Et le flag serait testé dans le destructeur et dans les opérateurs d'affectation.

Sinon … Il y aurait quelque part un référentiel que l'on pourrait regarder pour voir les commits/les diffs entre deux versions ? Cela serait bien plus simple pour faire des revues. Merci.

Ce sujet est verrouillé.