2010-08-09 6 views
7

J'ai récemment vu un morceau de code à comp.lang.C++ modéré retournant une référence d'un entier statique à partir d'une fonction. Le code était quelque chose comme çaOptimisation prématurée ou suis-je folle?

int& f() 
{ 
    static int x; 
    x++; 
    return x; 
} 

int main() 
{ 
    f()+=1; //A 
    f()=f()+1; //B 
    std::cout<<f(); 

} 

Quand je débogués l'application en utilisant mon sang-froid débogueur Visual Studio, j'ai vu un seul appel à la déclaration A et devinez ce que j'ai été choqué. J'ai toujours pensé que i+=1 était égal à i=i+1 alors f()+=1 serait égal à f()=f()+1 et je verrais deux appels à f(), mais j'ai vu un seul. C'est quoi ce truc? Suis-je fou ou mon débogueur est-il devenu fou ou est-ce le résultat d'une optimisation prématurée?

+4

'static int x' n'est pas initialisé. –

+1

Ce n'est pas lié à la question, cependant. – BjoernD

+1

@Rambo: Je pense que vous parlez de ce fil: http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/566222d9b756ecc5/3279970278f46946 –

Répondre

27

C'est ce que la norme dit à propos += et amis:

5.17-7: Le comportement d'une expression de la forme E1 op = E2 est équivalent à E1 = E1 op E2 sauf que E1 est évalué qu'une seule fois. [...]

Ainsi, le compilateur est juste sur ce point.

+4

Belle trouvaille _______________ :-) –

10

i+=1 est fonctionnellement le même que i=i+1. Il est implémenté différemment (essentiellement, il est conçu pour tirer parti de l'optimisation du niveau du processeur).

Mais essentiellement, le côté gauche est évalué une seule fois. Il donne une valeur l non-const, c'est tout ce dont il a besoin pour lire la valeur, en ajouter une et la réécrire.

Ceci est plus évident lorsque vous créez un opérateur surchargé pour un type personnalisé. operator+= modifie l'instance this. operator+ renvoie une nouvelle instance. Il est généralement recommandé (en C++) d'écrire oop + = first, puis d'écrire op + en fonction de cela.

(Notez que c'est applique uniquement à C++, C#, op+= est exactement comme vous le supposiez. Juste un raccourci pour op+, et vous ne pouvez pas créer votre propre op + = Il est automatiquement créé pour vous de l'Op +)

9

Votre pensée est logique mais pas correcte.

i += 1; 
// This is logically equivalent to: 
i = i + 1; 

Mais logiquement, les équivalences et les identités ne sont pas les mêmes.
Le code devrait être considéré comme ressemblant à ceci:

int& x = f(); 
x += x; 
// Now you can use logical equivalence. 
int& x= f(); 
x = x + 1; 

Le compilateur ne fera pas deux appels de fonctions, sauf si vous mettez explicitement deux appels de fonction dans le code. Si vous avez des effets secondaires dans vos fonctions (comme vous le faites) et que le compilateur a commencé à ajouter des durées supplémentaires pour voir les appels implicites, il serait très difficile de comprendre le flux du code et de rendre la maintenance très difficile.

3

f() renvoie une référence à l'entier statique. Puis += 1 ajoute un à cet emplacement mémoire – il n'y a pas besoin de l'appeler deux fois dans la déclaration A.

0

Dans chaque langue que j'ai vu qui supporte un opérateur + =, le compilateur évalue l'opérande du côté gauche une fois pour donner un type d'adresse qui est ensuite utilisé pour lire l'ancienne valeur et écrire le nouveau. L'opérateur + = n'est pas seulement du sucre syntaxique; Comme vous le constatez, il peut atteindre une sémantique d'expression qu'il serait difficile d'atteindre par d'autres moyens. Par ailleurs, les instructions "With" dans vb.net et Pascal ont toutes deux une fonctionnalité similaire. Une instruction comme:

 
' Assime Foo is an array of some type of structure, Bar is a function, and Boz is a variable. 
    With Foo(Bar(Boz)) 
    .Fnord = 9 
    .Quack = 10 
    End With 
va calculer l'adresse de Foo (Bar (Boz)), puis définir deux champs de cette structure aux valeurs neuf et dix. Il serait équivalent en C à

 
    { 
    FOOTYPE *tmp = Foo(Bar(Boz)); 
    tmp->Fnord = 9; 
    tmp->Quack = 10; 
    } 

mais vb.net et Pascal ne pas exposer le pointeur temporaire. Alors que l'on pourrait obtenir le même effet dans VB.net sans utiliser "Avec" pour contenir le résultat de Bar(), utiliser "Avec" permet d'éviter la variable temporaire.

+0

Apparemment, par ex. C# évalue deux fois le LHS comme James le souligne dans sa réponse. –