2009-06-24 14 views
14

Quand je compile le code suivant à l'aide g++Opérateurs de conversion de type référence: demander des problèmes?

class A {}; 

void foo(A&) {} 

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

Je reçois des 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 font beaucoup de sens pour moi. A() est juste une valeur temporaire, pas un emplacement assignable sur la pile, donc il ne semble pas avoir d'adresse. Si elle 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; } 
}; 

tout va bien! Ma question est de savoir si cela est même à distance. Qu'est-ce que this fait exactement quand A() est construit comme une valeur temporaire?

Je me donne une certaine confiance par le fait que

void foo(const A&) {} 

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

Répondre

16

Ce n'est pas qu'une adresse ne peut pas être prise (le compilateur peut toujours la placer sur la pile, ce qu'elle fait avec ref-to-const), c'est une question d'intention des programmeurs. Avec une interface qui prend un A &, il est dit "Je vais modifier ce qui est dans ce paramètre afin que vous puissiez lire après l'appel de la fonction". Si vous le passez temporairement, alors la chose "modifiée" n'existe pas après la fonction. C'est (probablement) une erreur de programmation, donc il est interdit. 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; 
} 

Cela ne compile pas, mais si pouvait se lier à des temporaires ref à non-const, il rassemblerait mais des résultats surprenants. Dans plus_one (f), f serait implicitement converti en un int temporaire, plus_one prendrait le temp et l'augmenterait, laissant le float f sous-jacent intact. Quand plus_one est retourné, cela n'aurait eu aucun effet. Ce n'est certainement pas ce que le programmeur voulait.


La règle est parfois incorrecte. Un exemple courant (décrit here) tente 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 une ref à non-const. Vos options sont le casser en deux lignes, ou appeler une méthode retournant une ref à non-const:

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

Je suis d'accord que ce genre de chose est généralement une erreur de programmeur. Dans le cas où vous êtes curieux, la classe que j'essaie de transmettre en tant que référence est une sorte de pointeur intelligent, qui a un membre de pointeur sous-jacent alloué. Je veux réellement que la fonction de réception fasse des choses avec le pointeur sous-jacent. Je veux juste m'assurer que le pointeur intelligent temporaire n'est pas détruit (en décrémentant le nombre de références) jusqu'à ce que la fonction de réception revienne. – Ben

+1

Si c'est votre intention, déclarez le pointeur intelligent sur la pile explicitement et transmettez-le ou transmettez-le par valeur. Si vous le transmettez par valeur, vous avez la certitude qu'il ne sera pas détruit avant la fin de la fonction. – MSN

+0

@Ben - ajouté quelques-uns à mon post à ce sujet. Je ne peux pas dire si c'est une bonne idée pour vous sans voir votre code, mais il y a quelques cas d'utilisation (et il ne sera pas détruit jusqu'à ce qu'il revienne) –

4

Lorsque vous affectez une valeur r à une référence const, vous êtes certain que le temporaire ne sera pas détruit tant que la référence n'est pas détruite. Lorsque vous affectez à une référence non-const, aucune garantie 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 en toute sécurité rejettera const-ness bon gré mal gré et attendre que les choses fonctionnent. Il existe différentes sémantiques sur les références const et non-const.

+0

En général, un temporaire est détruit à la fin de l'évaluation de l'expression qui l'a créé. Il y a deux exceptions quand la durée de vie d'une extension temporaire est étendue, l'une d'elles étant signalée par @Eclipse. Voir: http://en.cppreference.com/w/cpp/language/lifetime#Temporary_object_lifetime – winnetou

3

Un Gotcha que certaines personnes peuvent courir dans: le compilateur MSVC (compilateur Visual Studio, vérifier avec Visual Studio 2008) compilera ce code sans problèmes. Nous avions utilisé ce paradigme dans un projet pour des fonctions qui prenaient habituellement un argument (un morceau de données à digérer), mais qui voulaient parfois rechercher le morceau et restituer les 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 à la chaîne vide), et le troisième argument était pour les données de retour (référence par défaut à la liste vide du type désiré). Ce paradigme a fonctionné dans Visual Studio 2005 et 2008, et nous avons dû le refactoriser pour que la liste soit construite et retournée au lieu de la propriété par appel et mutée pour compiler avec g ++.

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