novembre
2009
Introduction
Au long de vos divagations programmistiques vous avez certainement dû apercevoir du code qui resemblait à ça :
for(int i = 0; i < 1000; ++i);
Mais quelle est donc la différence entre i++ et ++i? C’est ce que nous allons essayer de décrouvrir au long de cet article avec un petit bonus final…
Une version PDF de mon article est disponible ici si vous le souhaitez. (Petit soucie de provider, le PDF sera dispo directement la semaine prochaine…)
Une question d’objet temporaire
Tout d’abord, il faut considérer ces incrémentations dans la cadre d’une expression composée. Par exemple :
int i = 0; // Cas 1.
tableau[i++] = 0;
i = 0; // Cas 2.
tableau[++i] = 0;
Quelle est la différence entre ++i et i++ ?
* i++ : Appelée post-incrémentation, l’expression envoie la valeur de i à la fonction, puis incrémente sa valeur de 1. Dans le cadre de notre exemple :
– tableau[0] vaudra 0.
– i vaudra 1 après l’affectation.
* ++i : Appelée pré-incrementation, l’expression incrémente de 1 la valeur de i puis envoie sa nouvelle valeur à la fonction. Dans notre exemple:
– tableau[1] vaudra 0.
– i vaudra 1.
Il y a donc une différence énorme de signification dans les deux écritures et il faudra faire attention au comportement de notre porgramme lorsqu’on manie ce genre d’expressions.
Quel est la différence entre ++i et i++ dans une expression simple ?
Dans un code du type :
++i; // i = i + 1;
i++; // i = i + 1;
Les deux expressions ont exactement le même comportement et ne font qu’incrémenter la valeur de i de 1.
Pourquoi vaut-il mieux utiliser la pré-incrémentation que la post-incrémentation ?
C’est simplement une question de rapidité. En effet, la post-incrémentation va créer une variable temporaire pour s’exécuter ce que ne fera pas le pré-incrémentation. Qui dit variable temporaire, dit accès mémoire et donc perte de temps et de place.
Voici les équivalences :
tableau[i++] = 0;
// Equivaut a :
int i = 0;
int tmpi = i; // Une variable temporaire est cree.
tableau[tmpi] = 0;
i++;
et
tableau[++i] = 0;
// Equivaut a :
int i = 0;
i = i + 1;
tableau[i] = 0;
Tout ça pour ça ?
Une variable temporaire, c’est pas grande chose, mais rappelez vous de la boucle for : l’incrémentation est appelée à chaque itération ce qui, à terme peut ralentir l’exécution du programme.
Il faut tout de même savoir que la plupart du temps, le compilateur optimisera. Il y a cependant des cas o`u le compilateur ne saura pas si pré-incrémenter ne changera pas le comportement et ne fera donc rien alors qu’une optimisation aurait dûe être faite.
Comme précisé par Matthieu Brucher, le compilateur aura surtout du mal à optimiser dans le cas d’objets plus complexes et notamment des itérateurs (Cf STL).
D’après les tests effectué d’après l’observation de spiceguid, la pré-décrémentation est plus lente (encore que…) que la pré-incrémentation :
real 0m3.600s
user 0m3.590s
sys 0m0.000s
real 0m3.550s
user 0m3.440s
sys 0m0.000s
avec un code du type :
// Et
for(int i = 0; i < 1000000000; ++i);
Vous êtes prévenus maintenant !
Bonus : Surchage des opérateurs
Vous êtes vous déjà demandé comment faire la différence entre une post-opération et une pré-opération lorsqu’il faut surcharger l’opérateur unaire d’incrémentation (++ ou – -) ? Parce qu’à priori, le code est du genre :
// Code ... 3
}
Il y a une astuce ! En effet, pour surcharger une post-opération, il faut ajouter un paramètre inutile ! D’o‘u :
// Code ...
}
inline operator ++(int) { // Post-incrémentation
// Code ...
}
Conclusion
J’espère que ce premier article vous a plu et qu’il vous incitera à revenir régulièrement !
Références
C++ Cookbook
By Jeff Cogswell, Christopher Diggins, Ryan Stephens, Jonathan Turkanis
Publisher: O’Reilly
Pub Date: November 2005
ISBN: 0-596-00761-2
C++ Coding Standards: 101 Rules, Guidelines, and Best Practices
By Herb Sutter, Andrei Alexandrescu
Publisher: Addison Wesley Professional
Pub Date: October 25, 2004
ISBN: 0-321-11358-6
Et surêment plein d’autres…
Merci à Jey et à Paulo pour la relecture.\\
Merci aussi à Matthieu Brucher et spiceguid pour leurs observations très contructives.
5 Commentaires + Ajouter un commentaire
Commentaires récents
- Avez vous déjà vu ? Des mots clefs poilus ? Maintenant, oui ! dans
- Avez vous déjà vu ? Des mots clefs poilus ? Maintenant, oui ! dans
- Avez vous déjà vu ? Des mots clefs poilus ? Maintenant, oui ! dans
- Avez vous déjà vu ? Des mots clefs poilus ? Maintenant, oui ! dans
- Avez vous déjà vu ? Des mots clefs poilus ? Maintenant, oui ! dans
Bien vu mais j’obtient des résultats du type :
real 0m3.696s
user 0m3.680s
sys 0m0.000s
real 0m3.501s
user 0m3.420s
sys 0m0.000s
Bien sur, je n’ai pas fait de tests poussé, mais les résultats on l’air au mieux similaire, si l’incrémentation n’est pas plus rapide que la décrémentation. Ce qui semble assez logique finalement puisque l’arithmetique signée utilise le complément à 2 pour faire une soustraction et donc à priori plus de cycle. Je ne peux cependant pas vous garantir ça.
Mais j’ajoute ça à l’article !
Merci de votre observation.
Très bonne question ! Je ferais les tests en rentrant ce soir et j’updaterai l’article si nécessaire.
Avez-vous testé la décrémentation vs. l’incrémentation ?
L’intérêt de la décrémentation c’est que la comparaison avec 0 est potentiellement plus rapide que la comparaison avec 1000.
Certains cpu (pas x86) ont d’ailleurs une instruction spécifique qui combine décrémentation et branchement conditionnel en une seule opération.
Merci de la précision, j’ajoute ça à l’article !
En fait, pour des variables entières, le compilateur sait très bien optimiser les boucles for. En revanche, pour les itérateurs, ce n’est pas du tout le cas. Donc l’intérêt de la préincrémentation est surtout visible lorsqu’on effectue des boucles sur des objets plus complexes que les entiers.