Déplacer la sémantique en C ++ - Déplacer-retourner des variables locales

10

Ma compréhension est qu'en C ++ 11, lorsque vous renvoyez une variable locale d'une fonction par valeur, le compilateur est autorisé à traiter cette variable comme une référence de valeur r et à la `` déplacer '' hors de la fonction pour la renvoyer (si RVO / NRVO ne se produit pas à la place, bien sûr).

Ma question est, cela ne peut-il pas casser le code existant?

Considérez le code suivant:

#include <iostream>
#include <string>

struct bar
{
  bar(const std::string& str) : _str(str) {}
  bar(const bar&) = delete;
  bar(bar&& other) : _str(std::move(other._str)) {other._str = "Stolen";}
  void print() {std::cout << _str << std::endl;}

  std::string _str;
};

struct foo
{
  foo(bar& b) : _b(b) {}
  ~foo() {_b.print();}

  bar& _b;
};

bar foobar()
{
  bar b("Hello, World!");
  foo f(b);

  return std::move(b);
}

int main()
{
  foobar();
  return EXIT_SUCCESS;
}

Mes pensées étaient qu'il serait possible pour un destructeur d'un objet local de référencer l'objet qui se déplace implicitement, et donc de voir de manière inattendue un objet «vide». J'ai essayé de tester cela (voir http://ideone.com/ZURoeT ), mais j'ai obtenu le résultat «correct» sans l'explicite std::movedans foobar(). Je suppose que c'était dû à NRVO, mais je n'ai pas essayé de réorganiser le code pour le désactiver.

Ai-je raison de dire que cette transformation (provoquant un déplacement hors de la fonction) se produit implicitement et pourrait casser le code existant?

MISE À JOUR Voici un exemple qui illustre ce dont je parle. Les deux liens suivants sont pour le même code. http://ideone.com/4GFIRu - C ++ 03 http://ideone.com/FcL2Xj - C ++ 11

Si vous regardez la sortie, c'est différent.

Donc, je suppose que cette question devient maintenant, a-t-elle été prise en compte lors de l'ajout d'un déplacement implicite à la norme, et il a été décidé qu'il était OK d'ajouter ce changement de rupture car ce type de code est assez rare? Je me demande également si des compilateurs avertiront dans des cas comme celui-ci ...

Bwmat
la source
Un mouvement doit toujours laisser l'objet dans un état destructible.
Zan Lynx
Ouais, mais ce n'est pas la question. Le code pré-c ++ 11 pourrait supposer que la valeur d'une variable locale ne changerait pas simplement en la renvoyant, donc ce mouvement implicite pourrait briser cette hypothèse.
Bwmat
C'est ce que j'ai essayé d'élucider dans mon exemple; via des destructeurs, vous pouvez inspecter l'état (d'un sous-ensemble) des variables locales d'une fonction «après» l'exécution de l'instruction return, mais avant le retour effectif de la fonction.
Bwmat
C'est une excellente question avec l'exemple que vous avez ajouté. J'espère que cela obtiendra plus de réponses de pros qui pourront éclairer cela. La seule vraie rétroaction que je puisse donner est: c'est pourquoi les objets ne devraient généralement pas avoir de vues non propriétaires sur les données. Il existe en fait de nombreuses façons d'écrire du code d'apparence innocente qui se trompe lorsque vous donnez à des objets des vues sans propriétaire (pointeurs ou références brutes). Je peux développer cela dans une réponse appropriée si vous le souhaitez, mais je suppose que ce n'est pas de cela que vous voulez vraiment entendre parler. Et btw, on sait déjà que 11 peut casser le code existant, juste par exemple en définissant de nouveaux mots clés.
Nir Friedman
Oui, je sais que C ++ 11 n'a jamais prétendu ne casser aucun ancien code, mais c'est assez subtil et serait vraiment facile à manquer (pas d'erreurs de compilation, d'avertissements, de
défauts de

Réponses:

8

Scott Meyers a posté sur comp.lang.c ++ (août 2010) un problème où la génération implicite de constructeurs de mouvement pouvait casser les invariants de classe C ++ 03:

struct X
{
  // invariant: v.size() == 5
  X() : v(5) {}

  ~X() { std::cout << v[0] << std::endl; }

private:    
  std::vector<int> v;
};

int main()
{
    std::vector<X> y;
    y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}

Ici, le problème est qu'en C ++ 03, Xavait un invariant que son vmembre avait toujours 5 éléments. X::~X()comptait sur cet invariant, mais le constructeur de mouvement nouvellement introduit est passé de v, mettant ainsi sa longueur à zéro.

Cela est lié à votre exemple car l'invariant cassé n'est détecté que dans le Xdestructeur de (comme vous le dites, il est possible pour un destructeur d'un objet local de référencer l'objet qui est implicitement déplacé, et donc de voir de manière inattendue un objet vide ).

C ++ 11 essaie de trouver un équilibre entre casser une partie du code existant et fournir des optimisations utiles basées sur des constructeurs de déplacement.

Le comité a initialement décidé que les constructeurs de déplacement et les opérateurs d'affectation de déplacement devraient être générés par le compilateur lorsqu'ils ne sont pas fournis par l'utilisateur.

Puis a décidé que cela était en effet alarmant et a limité la génération automatique de constructeurs de mouvements et d'opérateurs d'affectation de mouvements de telle sorte qu'il est beaucoup moins probable, mais pas impossible, que le code existant se casse (par exemple, destructeur défini explicitement).

Il est tentant de penser qu'il est suffisant d'empêcher la génération de constructeurs de mouvements implicites lorsqu'un destructeur défini par l'utilisateur est présent ( N3153 - Implicit Move Must Go pour plus de détails).

Dans N3174 - Déplacer ou ne pas déplacer Stroupstrup dit:

Je considère que c'est un problème de conception de langage, plutôt qu'un simple problème de compatibilité descendante. Il est facile d'éviter de casser l'ancien code (par exemple, il suffit de supprimer les opérations de déplacement de C ++ 0x), mais je vois faire de C ++ 0x un meilleur langage en faisant des opérations de déplacement omniprésentes un objectif majeur pour lequel cela peut valoir la peine de casser du C + +98 code.

manlio
la source