Comment une référence non-const ne peut-elle pas se lier à un object temporaire?

Pourquoi est-il interdit d’obtenir une référence non-const à un object temporaire, quelle fonction getx() renvoie? Clairement, ceci est interdit par C ++ Standard mais je suis intéressé par le but de cette ressortingction, pas une référence à la norme.

 struct X { X& ref() { return *this; } }; X getx() { return X();} void g(X & x) {} int f() { const X& x = getx(); // OK X& x = getx(); // error X& x = getx().ref(); // OK g(getx()); //error g(getx().ref()); //OK return 0; } 
  1. Il est clair que la durée de vie de l’object ne peut en être la cause, car la référence constante à un object n’est pas interdite par le standard C ++.
  2. Il est clair que l’object temporaire n’est pas constant dans l’exemple ci-dessus, car les appels à des fonctions non constantes sont autorisés. Par exemple, ref() pourrait modifier l’object temporaire.
  3. De plus, ref() vous permet de tromper le compilateur et d’obtenir un lien vers cet object temporaire, ce qui résout notre problème.

En outre:

Ils disent que “assigner un object temporaire à la référence const étend la durée de vie de cet object” et “Rien n’est dit sur les références non-const”. Ma question supplémentaire L’atsortingbution suivante prolonge-t-elle la durée de vie de l’object temporaire?

 X& x = getx().ref(); // OK 

À partir de cet article de blog Visual C ++ sur les références rvalue :

… C ++ ne veut pas que vous modifiiez accidentellement des temporaires, mais appeler directement une fonction membre non-const sur une valeur modifiable est explicite, donc c’est permis …

Fondamentalement, vous ne devriez pas essayer de modifier les provisoires pour la simple raison qu’ils sont des objects temporaires et mourront à tout moment maintenant. La raison pour laquelle vous êtes autorisé à appeler des méthodes non constantes est que, bien, vous pouvez faire certaines choses “stupides” tant que vous savez ce que vous faites et que vous êtes explicite à ce sujet (comme utiliser reinterpret_cast). Mais si vous liez un temporaire à une référence non-const, vous pouvez continuer à le faire “pour toujours” juste pour que votre manipulation de l’object disparaisse, car quelque part, vous avez complètement oublié que c’était temporaire.

Si j’étais vous, je repenserais le design de mes fonctions. Pourquoi g () accepte-t-il la référence, modifie-t-il le paramètre? Si non, définissez-la comme référence, si oui, pourquoi essayez-vous d’y passer temporairement, cela ne vous dérange-t-il pas que vous modifiez temporairement? Pourquoi getx () retourne-t-il temporairement de toute façon? Si vous partagez avec nous votre véritable scénario et ce que vous essayez d’accomplir, vous pouvez obtenir de bonnes suggestions sur la manière de le faire.

Aller à l’encontre du langage et tromper le compilateur résout rarement les problèmes – cela crée généralement des problèmes.


Edit: Répondre aux questions dans le commentaire: 1) X& x = getx().ref(); // OK when will x die? X& x = getx().ref(); // OK when will x die? – Je ne sais pas et ça m’est égal, car c’est exactement ce que je veux dire par “aller à l’encontre de la langue”. Le langage dit “les temporels meurent à la fin de l’instruction, à moins qu’ils ne soient liés à la référence const, auquel cas ils meurent lorsque la référence est hors de scope”. En appliquant cette règle, il semble que x est déjà mort au début de la prochaine instruction, car il n’est pas lié à la référence const (le compilateur ne sait pas ce que ref () renvoie). C’est juste une supposition cependant.

2) J’ai clairement énoncé le but: vous n’êtes pas autorisé à modifier les provisoires, car cela n’a pas de sens (en ignorant les références C ++ 0x rvalue). La question “alors pourquoi suis-je autorisé à appeler des membres non-const?” est un bon, mais je n’ai pas de meilleure réponse que celle que j’ai déjà mentionnée ci-dessus.

3) Eh bien, si j’ai raison à propos de x dans X& x = getx().ref(); mourant à la fin de la déclaration, les problèmes sont évidents.

Quoi qu’il en soit, sur la base de votre question et de vos commentaires, je ne pense pas que ces réponses supplémentaires vous satisferont. Voici un dernier essai / résumé: Le comité C ++ a décidé qu’il n’était pas logique de modifier les provisoires, par conséquent, ils n’autorisaient pas la liaison avec des références non-const. Peut-être que la mise en œuvre du compilateur ou des problèmes historiques ont également été impliqués, je ne sais pas. Ensuite, un cas spécifique est apparu, et il a été décidé que contre toute attente, ils autoriseront toujours la modification directe en appelant la méthode non-const. Mais c’est une exception – vous n’êtes généralement pas autorisé à modifier les données temporaires. Oui, C ++ est souvent aussi étrange.

Dans votre code, getx() renvoie un object temporaire, appelé “rvalue”. Vous pouvez copier des valeurs dans des objects (ou des variables) ou les lier à des références const (qui prolongeront leur durée de vie jusqu’à la fin de la vie de la référence). Vous ne pouvez pas lier les valeurs à des références non const.

C’était une décision de conception délibérée pour empêcher les utilisateurs de modifier accidentellement un object qui va mourir à la fin de l’expression:

 g(getx()); // g() would modify an object without anyone being able to observe 

Si vous voulez faire cela, vous devrez soit faire une copie locale ou de l’object d’abord ou le lier à une référence const:

 X x1 = getx(); const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference g(x1); // fine g(x2); // can't bind a const reference to a non-const reference 

Notez que le prochain standard C ++ inclura des références rvalue. Ce que vous connaissez comme références devient donc appelé “références lvalue”. Vous serez autorisé à lier des valeurs Rval aux références et vous pourrez surcharger des fonctions sur “rvalue-ness”:

 void g(X&); // #1, takes an ordinary (lvalue) reference void g(X&&); // #2, takes an rvalue reference X x; g(x); // calls #1 g(getx()); // calls #2 g(X()); // calls #2, too 

L’idée derrière les références rvalue est que, puisque ces objects vont mourir de toute façon, vous pouvez tirer parti de ces connaissances et implémenter ce qu’on appelle la “sémantique de mouvement”, un certain type d’optimisation:

 class X { X(X&& rhs) : pimpl( rhs.pimpl ) // steal rhs' data... { rhs.pimpl = NULL; // ...and leave it empty, but deconstructible } data* pimpl; // you would use a smart ptr, of course }; X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty 

Ce que vous montrez, c’est que le chaînage des opérateurs est autorisé.

  X& x = getx().ref(); // OK 

L’expression est ‘getx (). Ref ();’ et ceci est exécuté jusqu’à la fin avant l’affectation à ‘x’.

Notez que getx () ne renvoie pas une référence mais un object entièrement formé dans le contexte local. L’object est temporaire mais il n’est pas const, ce qui vous permet d’appeler d’autres méthodes pour calculer une valeur ou avoir d’autres effets secondaires.

 // It would allow things like this. getPipeline().procInstr(1).procInstr(2).procInstr(3); // or more commonly std::cout << getManiplator() << 5; 

Regardez la fin de cette réponse pour un meilleur exemple de cela

Vous ne pouvez pas lier un temporaire à une référence, car cela générera une référence à un object qui sera détruit à la fin de l'expression, vous laissant ainsi une référence en suspens (qui est désordonnée et le standard n'aime pas trop).

La valeur renvoyée par ref () est une référence valide mais la méthode ne prête aucune attention à la durée de vie de l'object qu'elle retourne (car elle ne peut pas avoir cette information dans son contexte). Vous venez de faire l'équivalent de:

 x& = const_cast(getX()); 

La raison pour laquelle cela est acceptable avec une référence const à un object temporaire est que la norme étend la durée de vie du temporaire à la durée de vie de la référence afin que la durée de vie des objects temporaires soit prolongée au-delà de la fin de l'instruction.

Donc, la seule question qui rest est la suivante: pourquoi la norme ne veut-elle pas autoriser la référence à des provisoires pour prolonger la vie de l'object au-delà de la fin de l'énoncé?

Je crois que c'est parce que cela rendrait le compilateur très difficile à corriger pour les objects temporaires. Cela a été fait pour les références const aux temporaires car cela a une utilisation limitée et vous oblige donc à faire une copie de l'object pour faire quelque chose d'utile mais fournit des fonctionnalités limitées.

Pensez à cette situation:

 int getI() { return 5;} int x& = getI(); x++; // Note x is an alias to a variable. What variable are you updating. 

L'extension de la durée de vie de cet object temporaire sera très déroutante.
Alors que le suivant:

 int const& y = getI(); 

Vous donnera le code qu'il est intuitif à utiliser et à comprendre.

Si vous souhaitez modifier la valeur, vous devez renvoyer la valeur à une variable. Si vous essayez d'éviter le coût de la copie de l'obéjct de la fonction (comme il semble que l'object est reconstitué (techniquement c'est le cas)). Alors, ne vous embêtez pas, le compilateur est très performant en matière d'optimisation de la valeur de retour.

Pourquoi est discuté dans la FAQ C ++ (mine en gras ):

En C ++, les références non const peuvent se lier à lvalues ​​et les références const peuvent se lier à lvalues ​​ou rvalues, mais rien ne peut se lier à une valeur non const. C’est pour protéger les gens de modifier les valeurs des composants temporaires qui sont détruits avant que leur nouvelle valeur puisse être utilisée . Par exemple:

 void incr(int& a) { ++a; } int i = 0; incr(i); // i becomes 1 incr(0); // error: 0 is not an lvalue 

Si cette incr (0) était permise, soit une valeur temporaire que personne n’a jamais vue ne serait incrémentée ou, bien pire, la valeur de 0 deviendrait 1. Cette dernière semble idiote, mais il existait un bogue comme celui des premiers compilateurs Fortran à côté d’un emplacement mémoire pour contenir la valeur 0.

Le principal problème est que

 g(getx()); //error 

est une erreur logique: g modifie le résultat de getx() mais vous n’avez aucune chance d’examiner l’object modifié. Si g n’a pas besoin de modifier son paramètre alors il n’aurait pas nécessité de référence lvalue, il aurait pu prendre le paramètre par valeur ou par référence const.

 const X& x = getx(); // OK 

est valide parce que vous avez parfois besoin de réutiliser le résultat d’une expression, et il est assez clair que vous avez affaire à un object temporaire.

Cependant, il n’est pas possible de faire

 X& x = getx(); // error 

valide sans que g(getx()) valide, ce que les concepteurs de langage essayaient d’éviter d’abord.

 g(getx().ref()); //OK 

est valide parce que les méthodes ne connaissent que la constance de this , elles ne savent pas si elles sont appelées sur une lvalue ou sur une rvalue.

Comme toujours en C ++, vous avez une solution de contournement pour cette règle, mais vous devez signaler au compilateur que vous savez ce que vous faites en étant explicite:

 g(const_cast(getX())); 

On a clairement répondu à la question initiale de savoir pourquoi cela n’était pas autorisé: “parce qu’il s’agit très probablement d’une erreur”.

FWIW, j’ai pensé que je pourrais montrer comment faire, même si je ne pense pas que ce soit une bonne technique.

La raison pour laquelle je veux parfois passer un temporaire à une méthode prenant une référence non-const est de jeter intentionnellement une valeur renvoyée par référence dont la méthode appelante ne se soucie pas. Quelque chose comme ça:

 // Assuming: void Person::GetNameAndAddr(std::ssortingng &name, std::ssortingng &addr); ssortingng name; person.GetNameAndAddr(name, ssortingng()); // don't care about addr 

Comme expliqué dans les réponses précédentes, cela ne comstack pas. Mais cela comstack et fonctionne correctement (avec mon compilateur):

 person.GetNameAndAddr(name, const_cast(static_cast(ssortingng()))); 

Cela montre simplement que vous pouvez utiliser le casting pour mentir au compilateur. Evidemment, il serait beaucoup plus propre de déclarer et de transmettre une variable automatique inutilisée:

 ssortingng name; ssortingng unused; person.GetNameAndAddr(name, unused); // don't care about addr 

Cette technique introduit une variable locale inutile dans la scope de la méthode. Si, pour une raison quelconque, vous souhaitez empêcher son utilisation ultérieure dans la méthode, par exemple pour éviter toute confusion ou erreur, vous pouvez la masquer dans un bloc local:

 ssortingng name; { ssortingng unused; person.GetNameAndAddr(name, unused); // don't care about addr } 

– Chris

Pourquoi voudriez-vous jamais X& x = getx(); ? Utilisez simplement X x = getx(); et compter sur RVO.

La solution de rechange maléfique implique le mot-clé «mutable». En fait, être mal est un exercice pour le lecteur. Ou voir ici: http://www.ddj.com/cpp/184403758

Excellente question, et voici ma tentative de réponse plus concise (car beaucoup d’informations utiles sont dans les commentaires et difficiles à creuser dans le bruit.)

Toute référence liée directement à un temporaire prolongera sa durée de vie [12.2.5]. Par contre, une référence initialisée avec une autre référence ne le sera pas (même si c’est finalement le même temporaire). Cela a du sens (le compilateur ne sait pas à quoi cette référence se réfère en fin de compte).

Mais toute cette idée est extrêmement déroutante. Par exemple, const X &x = X(); rendra le temporaire aussi long que la référence x , mais const X &x = X().ref(); ne sera PAS (qui sait ce que ref() réellement retourné). Dans ce dernier cas, le destructeur de X est appelé à la fin de cette ligne. (Ceci est observable avec un destructeur non sortingvial.)

Donc, cela semble généralement déroutant et dangereux (pourquoi compliquer les règles sur la durée de vie des objects?), Mais il y avait probablement un besoin au moins pour les références const, donc la norme leur impose ce comportement.

[De sbi comment]: Notez que le fait de le lier à une référence const améliore la durée de vie d’un temporaire. Il s’agit d’une exception qui a été ajoutée délibérément (TTBOMK pour permettre des optimisations manuelles). Il n’y avait pas d’exception ajoutée pour les références non-const, car la liaison d’une référence temporaire à une référence non-const était très probablement une erreur du programmeur.

Tous les temporaires persistent jusqu’à la fin de la pleine expression. Pour les utiliser, cependant, vous avez besoin d’une astuce comme avec ref() . C’est légal Il ne semble pas y avoir de raison de sauter à travers le cerceau supplémentaire, sauf pour rappeler au programmeur que quelque chose d’inhabituel se produit (à savoir un paramètre de référence dont les modifications seront rapidement perdues).

[Un autre commentaire de sbi ] La raison que Stroustrup donne (en D & E) pour interdire la liaison de rvalues ​​à des références non-const est que, si g () d’Alexey modifie l’object (que vous attendez d’une fonction prenant un non-const) référence), cela modifierait un object qui va mourir, donc personne ne pourrait obtenir la valeur modifiée de toute façon. Il dit que cela, très probablement, est une erreur.

“Il est clair que l’object temporaire n’est pas constant dans l’exemple ci-dessus, car les appels à des fonctions non constantes sont autorisés. Par exemple, ref () pourrait modifier l’object temporaire.”

Dans votre exemple, getX () ne renvoie pas de const X, vous pouvez donc appeler ref () de la même manière que vous pourriez appeler X (). Ref (). Vous retournez une référence non-const et pouvez donc appeler des méthodes non-const. Ce que vous ne pouvez pas faire est d’affecter la référence à une référence non-const.

Avec SadSidos commenter, cela rend vos trois points incorrects.

J’ai un scénario que j’aimerais partager où j’aimerais pouvoir faire ce que demande Alexey. Dans un plugin Maya C ++, je dois faire le shenanigan suivant pour obtenir une valeur dans un atsortingbut de noeud:

 MFnDoubleArrayData myArrayData; MObject myArrayObj = myArrayData.create(myArray); MPlug myPlug = myNode.findPlug(atsortingbuteName); myPlug.setValue(myArrayObj); 

C’est fastidieux d’écrire, alors j’ai écrit les fonctions d’aide suivantes:

 MPlug operator | (MFnDependencyNode& node, MObject& atsortingbute){ MStatus status; MPlug returnValue = node.findPlug(atsortingbute, &status); return returnValue; } void operator << (MPlug& plug, MDoubleArray& doubleArray){ MStatus status; MFnDoubleArrayData doubleArrayData; MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status); status = plug.setValue(doubleArrayObject); } 

Et maintenant je peux écrire le code depuis le début du post comme:

 (myNode | atsortingbuteName) << myArray; 

Le problème est qu'il ne comstack pas en dehors de Visual C ++, car il essaie de lier la variable temporaire renvoyée par | opérateur à la référence MPlug de l'opérateur <<. Je voudrais que ce soit une référence car ce code est appelé plusieurs fois et je préfère ne pas avoir autant de copie de MPlug. J'ai seulement besoin de l'objet temporaire pour vivre jusqu'à la fin de la deuxième fonction.

Eh bien, c'est mon scénario. Je pensais juste que je montrerais un exemple où on aimerait faire ce que Alexey décrit. Je salue toutes les critiques et suggestions!

Merci.