Question

Quand je compile le code suivant avec g++

class A {};

void foo(A&) {}

int main()
{
  foo(A());
  return 0;
}

Je reçois les messages d'erreur suivants:

> g++ test.cpp -o test     
test.cpp: In function ‘int main()’:
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’

Après réflexion, ces erreurs me paraissent tout à fait logiques. A() est juste une valeur temporaire, pas un emplacement assignable sur la pile, de sorte qu'il ne semble pas avoir d'adresse. S'il n'a pas d'adresse, je ne peux pas y faire référence. D'accord, d'accord.

Mais attendez! Si j'ajoute l'opérateur de conversion suivant à la classe A

class A
{
public:
  operator A&() { return *this; }
};

alors tout va bien! Ma question est de savoir si cela même à distance sécuritaire. Qu'est-ce que this désigne exactement quand const est construit comme une valeur temporaire?

Je suis confiant du fait que

void foo(const A&) {}

peut accepter les valeurs temporaires en fonction de const A& et de tous les autres compilateurs que j'ai utilisés. Le mot clé A& peut toujours être supprimé, donc je serais surpris de constater des différences sémantiques réelles entre un paramètre <=> et un paramètre <=>. Donc, je suppose que c’est une autre façon de poser ma question: pourquoi une <=> référence à une valeur temporaire est-elle considérée comme sûre par le compilateur alors qu’une non-<=> référence ne l’est pas?

Était-ce utile?

La solution

Ce n’est pas qu’une adresse ne puisse être prise (le compilateur peut toujours l’ordonner sur la pile, ce qu’il fait avec ref-to-const), c’est une question d’intention du programmeur. Avec une interface prenant un A &, Elle dit & ", Je modifierai le contenu de ce paramètre afin que vous puissiez lire après l'appel de la fonction &"; Si vous lui passez un temporaire, alors la chose qu'il & "Modifie &"; n'existe pas après la fonction. Ceci est (probablement) une erreur de programmation, elle est donc interdite. Par exemple, considérons:

void plus_one(int & x) { ++x; }

int main() {
   int x = 2;
   float f = 10.0;

   plus_one(x); plus_one(f);

   cout << x << endl << f << endl;
}

Ceci ne compile pas, mais si les temporaires pouvaient se lier à un ref-to-non-const, cela compilerait mais aurait des résultats surprenants. Dans plus_one (f), f serait implicitement converti en un entier temporaire, plus_one prendrait la température et l'incrémenterait, laissant le flottant sous-jacent intact. Quand plus_one est revenu, cela n'aurait eu aucun effet. Ce n’est certainement pas ce que le programmeur avait prévu.

La règle gâche occasionnellement. Un exemple courant (décrit ici ) , essaie d’ouvrir un fichier, d’imprimer quelque chose et de le fermer. Vous voudriez pouvoir faire:

ofstream("bar.t") << "flah";

Mais vous ne pouvez pas parce que l'opérateur < < prend un ref-to-non-const. Vos options sont diviser en deux lignes ou appeler une méthode renvoyant un ref-to-non-const:

ofstream("bar.t").flush() << "flah";

Autres conseils

Lorsque vous affectez une valeur r à une référence const, vous avez la garantie que le temporaire ne sera pas détruit avant la destruction de la référence. Lorsque vous affectez une référence non-const, aucune garantie de ce type n'est faite.

int main()
{
   const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope.
   A& a1 = A(); // You can't do this.
}

Vous ne pouvez pas vous débarrasser de Const-Ness en toute sécurité et vous attendre à ce que les choses fonctionnent. Il existe différentes sémantiques sur les références const et non-const.

Un piège que certaines personnes peuvent rencontrer: le compilateur MSVC (compilateur Visual Studio, vérifié avec Visual Studio 2008) compilera ce code sans aucun problème. Nous utilisions ce paradigme dans un projet pour des fonctions qui prenaient généralement un argument (un bloc de données à digérer), mais voulaient parfois rechercher le bloc et donner des résultats à l'appelant. L'autre mode était activé en prenant trois arguments - le deuxième argument était l'information à rechercher (référence par défaut à une chaîne vide), et le troisième argument concernait les données renvoyées (référence par défaut à une liste vide du type souhaité).

Ce paradigme a fonctionné dans Visual Studio 2005 et 2008 et nous avons dû le refactoriser afin que la liste soit construite et renvoyée au lieu d'être possédée par l'appelant et mutée pour être compilée avec g ++.

S'il existe un moyen de configurer les commutateurs du compilateur pour interdire ce type de comportement dans MSVC ou pour l'autoriser dans g ++, je serais ravi de le savoir; la permissivité du compilateur MSVC / le caractère restrictif du compilateur g ++ ajoute des complications au portage du code.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top