web-dev-qa-db-fra.com

Comment se fait-il qu'une référence non-const ne puisse pas se lier à un objet temporaire?

Pourquoi n'est-il pas autorisé à obtenir une référence non const à un objet temporaire, quelle fonction getx() renvoie? Clairement, cela est interdit par la norme C++ mais je m'intéresse à l'objectif d'une telle restriction, 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'objet ne peut en être la cause, car la référence constante à un objet est non interdite par la norme C++.
  2. Il est clair que l'objet 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'objet temporaire.
  3. De plus, ref() vous permet de tromper le compilateur et d'obtenir un lien vers cet objet temporaire, ce qui résout notre problème.

En plus:

Ils disent "assigner un objet temporaire à la référence const prolonge la durée de vie de cet objet" et "rien n'est dit à propos des références non const". Mon question supplémentaire. L'affectation suivante prolonge-t-elle la durée de vie de l'objet temporaire?

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

Dans votre code, getx() renvoie un objet temporaire, appelé "rvalue". Vous pouvez copier des valeurs dans des objets (variables.) Ou les lier à des références const (ce qui prolongera leur durée de vie jusqu'à la fin de la vie de la référence). Vous ne pouvez pas lier des valeurs à des références non constantes.

Il s'agissait d'une décision de conception délibérée visant à empêcher les utilisateurs de modifier accidentellement un objet 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 d'abord faire une copie locale ou de l'objet 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 la prochaine norme C++ inclura des références rvalue. Ce que vous connaissez comme références est donc en train de s'appeler "références lvalue". Vous serez autorisé à lier des rvalues ​​à des références de rvalue et vous pouvez 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 objets vont de toute façon mourir, vous pouvez tirer parti de ces connaissances et mettre en œuvre ce que l'on appelle "la sémantique de déplacement", 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
34
sbi

Ce que vous montrez, c'est que l'enchaînement d'opérateurs est autorisé.

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

L'expression est 'getx (). Ref ();' et ceci est exécuté jusqu'à achèvement avant l'affectation à 'x'.

Notez que getx () ne renvoie pas une référence mais un objet complètement formé dans le contexte local. L'objet est temporaire, mais il n'est pas , ce qui vous permet d'appeler d'autres méthodes pour calculer une valeur ou faire en sorte que d'autres effets secondaires se produisent.

// 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 ceci

Vous pouvez ne pas lier un temporaire à une référence, car cela générera une référence à un objet qui sera détruit à la fin de l'expression, vous laissant ainsi avec une référence pendante (qui est désordonné et la norme n'aime pas désordonné).

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'objet renvoyé (car il ne peut pas avoir cette information dans son contexte). Vous venez de faire l'équivalent de:

x& = const_cast<x&>(getX());

La raison pour laquelle il est correct de le faire avec une référence const à un objet temporaire est que la norme étend la durée de vie du temporaire à la durée de vie de la référence, de sorte que la durée de vie des objets temporaires est prolongée au-delà de la fin de l'instruction.

La seule question qui reste est donc de savoir pourquoi la norme ne veut pas permettre à la référence aux temporaires de prolonger la vie de l’objet au-delà de la fin de la déclaration.

Je pense que c'est parce que cela rendrait le compilateur très difficile à obtenir pour les objets temporaires. Cela a été fait pour les références const aux temporaries car cela a un usage limité et vous oblige donc à faire une copie de l'objet pour faire quelque chose d'utile mais fournit quelques 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.

Prolonger la durée de vie de cet objet temporaire sera très déroutant.
Alors que:

int const& y = getI();

Vous donnera un code intuitif à utiliser et à comprendre.

Si vous souhaitez modifier la valeur, vous devez la renvoyer dans une variable. Si vous essayez d'éviter le coût de la copie de l'objet depuis la fonction (car il semble que l'objet est une copie construite (techniquement, il l'est)). Alors ne vous inquiétez pas le compilateur est très bon à 'Optimisation de la valeur de retour'

16
Martin York

Pourquoi est discuté dans le C++ FAQ ( en gras mien):

En C++, les références non const peuvent être liées à des valeurs lvalues ​​et les références const peuvent être liées à des valeurs lvalues ​​ou rvalues, mais rien ne permet de se lier à une valeur non const. C'est pour empêcher les gens de changer les valeurs des temporaires 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émentation (0) était autorisée, une valeur temporaire que personne ne vit jamais serait incrémentée ou, pire encore, la valeur de 0 deviendrait 1. Ce dernier semble ridicule, mais il existait en fait un bogue semblable à celui des premiers compilateurs Fortran. mettre de côté un emplacement de mémoire pour contenir la valeur 0.

9
Tony Delroy

Le problème principal 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’objet modifié. Si g n'avait pas besoin de modifier son paramètre, il n'aurait pas besoin d'une référence à lvalue, il aurait pu le prendre par valeur ou par référence const.

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

est valide parce que vous devez parfois réutiliser le résultat d'une expression et qu'il est assez clair que vous traitez avec un objet temporaire.

Cependant, il n'est pas possible de faire

X& x = getx(); // error

valide sans rendre g(getx()) valide, ce que les concepteurs de langage essayaient d'éviter en premier lieu.

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

est valide car les méthodes ne connaissent que la const-nessité de this, elles ne savent pas si elles sont appelées sur une valeur lvalue ou sur une valeur 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<x&>(getX()));
6
Dan Berindei

On dirait que la question initiale de pourquoi ceci n'est pas autorisé a reçu une réponse claire: "car c'est très probablement une erreur".

FWIW, je pensais montrer comment que cela pourrait être fait, 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 qui ne tient pas à la méthode d'appel. Quelque chose comme ça:

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

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

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

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

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

Cette technique introduit une variable locale inutile dans la portée 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 le masquer dans un bloc local:

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

- Chris

5
Chris Pearson

La solution de contournement mal implique le mot clé "mutable". En réalité, faire le mal est un exercice pour le lecteur. Ou voir ici: http://www.ddj.com/cpp/184403758

4
paul

Pourquoi voudriez-vous un jour X& x = getx();? Il suffit d’utiliser X x = getx(); et de faire appel à RVO.

4
DevFred

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

Toute référence liée directement à un temporaire prolongera sa durée de vie [12.2.5]. D'autre part, une référence initialisée avec une autre référence 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 finalement).

Mais toute cette idée est extrêmement déroutante. Par exemple. const X &x = X(); fera en sorte que le temporaire dure aussi longtemps que la référence x, mais const X &x = X().ref(); ne le fera PAS (qui sait ce que ref() est 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 trivial.)

Cela semble donc généralement déroutant et dangereux (pourquoi compliquer les règles relatives à la durée de vie des objets?), Mais il était vraisemblable qu'il y avait au moins un besoin de références const, aussi la norme définit-elle ce comportement pour elles.

[De sbi commentaire]: Notez que le fait de le lier à une référence const améliore la durée de vie d'un temporaire est une exception qui a été ajoutée délibérément (TTBOMK afin de permettre des optimisations manuelles). Aucune exception n'a été ajoutée pour les références non constantes, car la liaison d'une référence temporaire à une référence non constait très probablement une erreur de programmeur.

Tous les temporaires persistent jusqu'à la fin de la pleine expression. Cependant, pour les utiliser, vous avez besoin d’une astuce similaire à celle que vous avez avec ref(). C'est légal. Il ne semble pas y avoir de bonne raison pour que le cercle supplémentaire saute, sauf pour rappeler au programmeur qu'il se passe quelque chose d'inhabituel (à savoir un paramètre de référence dont les modifications seront rapidement perdues).

[Another sbi comment] La raison pour laquelle Stroustrup donne (en D & E) le refus de lier la liaison des valeurs à des références non const est que, si Alexey's g() modifierait l'objet (que vous attendriez d'une fonction prenant une référence non-const), il modifierait un objet qui va mourir, afin que personne ne puisse obtenir la valeur modifiée de toute façon. Il dit que ceci est très probablement une erreur .

3
DS.

"Il est clair que l'objet 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'objet temporaire."

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

Avec le commentaire SadSidos, cela rend vos trois points incorrects.

2
Patrick

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

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

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

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &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 en tant que:

(myNode | attributeName) << myArray;

Le problème est qu'il ne compile pas en dehors de Visual C++, car il tente de lier la variable temporaire renvoyée à partir de | opérateur à la référence MPlug de l'opérateur <<. J'aimerais que ce soit une référence, car ce code est appelé plusieurs fois et je préférerais que MPlug ne soit pas copié autant. Je n'ai besoin que de l'objet temporaire pour vivre jusqu'à la fin de la deuxième fonction.

Eh bien, c'est mon scénario. Juste pensé que je montrerais un exemple où on aimerait faire ce que Alexey décrit. Je me félicite de toutes les critiques et suggestions!

Merci.

1
Robert Trussardi