Outils d'utilisateurs

Outils du Site


references

Différences

Cette page vous donne les différences entre la révision choisie et la version actuelle de la page.

Lien vers cette vue

references [2016/07/09 12:50]
gbdivers
references [2018/08/19 16:23] (Version actuelle)
gbdivers
Ligne 4: Ligne 4:
 ====== Copies, déplacements et indirections ====== ====== Copies, déplacements et indirections ======
  
-Dans le chapitre precedent, vous avez vu comment echanger des informations avec une fonction, en utilisant les parametres de fonction et le retour de fonction.+Dans le chapitre précédent, vous avez vu comment échanger des informations avec une fonction, en utilisant les paramètres de fonction et le retour de fonction.
  
-Le type d'echange que vous avez vu s'appelle un passage par valeur. Cela consiste a partir de l'objet qui se trouve dans la fonction appelante, et d'en faire une copie qui sera utilisable dans la fonction appelee.+Le type d’échange que vous avez vu s'appelle un passage par valeur. Cela consiste a partir de l'objet qui se trouve dans la fonction appelante, et d'en faire une copie qui sera utilisable dans la fonction appelée.
  
 <code cpp> <code cpp>
 void f(int i) { void f(int i) {
     // i est une nouvelle "variable", accessible uniquement dans la fonction f     // i est une nouvelle "variable", accessible uniquement dans la fonction f
-    // et qui contient la meme valeur que la variable j de la fonction g.+    // et qui contient la même valeur que la variable j de la fonction g.
 } }
  
Ligne 20: Ligne 20:
 </code> </code>
  
-La valeur est copiee lors de l'appel de la fonction f, ce qui implique que les eventuelles modifications du parametre ''i'' ne seront pas repercutees sur la variable ''j''.+La valeur est copiée lors de l'appel de la fonction f, ce qui implique que les éventuelles modifications du paramètre ''i'' ne seront pas répercutées sur la variable ''j''.
  
-Il existe d'autres facon de transmettre une information dans une fonction, il convient donc de detailler d'abord les concepts de copie, de deplacement et d'indirection.+Il existe d'autres façon de transmettre une information dans une fonction, il convient donc de détailler d'abord les concepts de copie, de déplacement et d'indirection.
  
-===== Copie et deplacement =====+===== Copie et déplacement =====
  
 ==== La copie d'objets ==== ==== La copie d'objets ====
  
-La copie consiste donc a creer un nouvel objet, identique a un objet existant. Ces deux objets seront independants, c'est a dire que si l'un des objets est modifie ou detruit, l'autre objet ne sera pas modifie.+La copie consiste donc a creer un nouvel objet, identique a un objet existant. Ces deux objets seront indépendants, c'est a dire que si l'un des objets est modifié ou détruit, l'autre objet ne sera pas modifie.
  
 <code cpp main.cpp> <code cpp main.cpp>
Ligne 35: Ligne 35:
 int main() { int main() {
     const int i { 123 };     const int i { 123 };
-    int j { i };  // j est une copie de i, elle contient la meme valeur+    int j { i };  // j est une copie de i, elle contient la même valeur
     std::cout << "i=" << i << ", j=" << j << std::endl;     std::cout << "i=" << i << ", j=" << j << std::endl;
     j = 456;     j = 456;
Ligne 49: Ligne 49:
 </code> </code>
  
-Tous les objets ne sont pas copiables. Les types fondamentaux (''int'', ''double'', ''float'', etc) sont copiables, ainsi que la tres grande majorite des classes de la bibliotheque standard.+{{ :copie-move-1.png |}}
  
-<note>Souvenez vous, cela a ete aborde dans le chapitre [[collection2|]], la majorite des classes de la bibliotheque standard possedent une semantique de valeur et sont copiables. Un exemple de classe non copiable est ''std::unique_ptr''. Cela sera detaille dans la partie sur la programmation objet.</note>+Tous les objets ne sont pas copiables. Les types fondamentaux (''int'', ''double'', ''float'', etc) sont copiables, ainsi que la très grande majorité des classes de la bibliothèque standard. 
 + 
 +<note>Souvenez vous, cela a été abordé dans le chapitre [[collection2|]], la majorité des classes de la bibliothèque standard possèdent une sémantique de valeur et sont copiables. Un exemple de classe non copiable est ''std::unique_ptr''. Cela sera détaillé dans la partie sur la programmation objet.</note>
  
  
 ==== Le deplacement d'objets ==== ==== Le deplacement d'objets ====
  
-Le deplacement d'objets consiste a deplacer (//move//) un objet depuis une variable vers une autre. L'objet n'est pas modifie dans cette operation, il est conserve a l'identique. Cette operation peut etre realisee en utilisant la fonction ''std::move''.+Le déplacement d'objets consiste a déplacer (//move//) un objet depuis une variable vers une autre. L'objet n'est pas modifié dans cette opération, il est conservé a l'identique. Cette opération peut être réalisée en utilisant la fonction ''std::move''.
  
 <code cpp main.cpp> <code cpp main.cpp>
Ligne 67: Ligne 69:
 } }
 </code> </code>
 +
 +{{ :copie-move-2.png |}}
  
 <note>Cette notion a aussi ete vu rapidement dans [[collection2|]] et sera detaille dans la partie sur la programmation objet.</note> <note>Cette notion a aussi ete vu rapidement dans [[collection2|]] et sera detaille dans la partie sur la programmation objet.</note>
Ligne 111: Ligne 115:
 #include <iostream> #include <iostream>
  
-int f(int i) { +int f(int j) { 
-    return + 456;+    return + 456;
 } }
  
 int main() { int main() {
-    int { 123 }; +    int { 123 }; 
-    = f(j); +    = f(i); 
-    std::cout << << std::endl;+    std::cout << << std::endl;
 } }
 </code> </code>
Ligne 128: Ligne 132:
 </code> </code>
  
-Dans ce code, l'objet initialement dans la variable ''j'' est dans un premier temps copie dans la variable ''i''de la fonction ''f'', puis le resultat est deplacer depuis la variable ''i'' de la fonction ''f'' vers la variable ''j'' de la fonction ''main''. Cela fait beaucoup de manipulation d'objets.+Dans ce code, l'objet initialement dans la variable ''i'' est dans un premier temps copie dans la variable ''j''de la fonction ''f'', puis le resultat est deplacer depuis la variable ''j'' de la fonction ''f'' vers la variable ''i'' de la fonction ''main''. Cela fait beaucoup de manipulation d'objets.
  
-Les indirections sont un moyen d'acceder une variable a distance, sans devoir faire de copie ou de deplacement. Utiliser une indirection revient a utiliser indirectement une autre variable.+Les indirections sont un moyen d’accéder un objet, sans devoir faire de copie ou de deplacement. Travailler sur une indirection revient a travailler sur l'objet indirectement. Toute modification sur l'indirection sera visible dans la variable d'origine et vice-versa.
  
 Le code precedent peut etre modifie de la facon suivante : Le code precedent peut etre modifie de la facon suivante :
  
 <code cpp> <code cpp>
-void f(int & i) {  // notez bien l'ajout de & ici +void f(int & j) {  // notez bien l'ajout de & ici 
-    += 456;+    += 456;
 } }
  
 int main() { int main() {
-    int { 123 }; +    int { 123 }; 
-    f(j); +    f(i); 
-    std::cout << << std::endl;+    std::cout << << std::endl;
 } }
 </code> </code>
Ligne 152: Ligne 156:
 </code> </code>
  
-Dans ce code, la variable ''i'' dans la fonction ''f'' est une indirection (une reference) vers la variable ''j'' de la fonction ''main''. Utiliser ''i'' revient a utiliser indirectement ''j'', la modification realisee sur ''i'' est en fait realisee sur ''j''.+Dans ce code, la variable ''j'' dans la fonction ''f'' est une indirection (une reference) vers la variable ''i'' de la fonction ''main''. Utiliser ''j'' revient a utiliser indirectement ''i'', la modification realisee sur ''j'' est en fait realisee sur ''i''. 
 + 
 +{{ :copie-move-3.png |}}
  
 Il n'y a pas de copie ou de deplacement dans ce code. Les indirections seront donc souvent utilisees pour eviter la copie d'objets complexes ou pour modifier un objet dans une fonction. Lorsque la fonction ne doit pas modifier l'objet, la reference sera mis en constante avec le mot-cle ''const''. Il n'y a pas de copie ou de deplacement dans ce code. Les indirections seront donc souvent utilisees pour eviter la copie d'objets complexes ou pour modifier un objet dans une fonction. Lorsque la fonction ne doit pas modifier l'objet, la reference sera mis en constante avec le mot-cle ''const''.
Ligne 203: Ligne 209:
 <note>**Semantiques** <note>**Semantiques**
  
-Une semantique est le sens qui est donne a un concept, c'est a dire l'ensemble des operations que ce concept permet de faire.+Une sémantique est le sens qui est donne a un concept, c'est a dire l'ensemble des operations que ce concept permet de faire.
  
-Une "indirection a semantique de reference" est donc une indirection qui n'est pas forcement une reference, mais qui se manipulera comme une reference. Et de meme, une "indirection a semantique de pointeur" est une indirection qui se manipule comme un pointeur, sans forcement etre un pointeur. (Les pointeurs seront vu dans la partie sur la programmation orientee objet).+Une "indirection a semantique de reference" est donc une indirection qui n'est pas forcement une reference, mais qui se manipulera comme une reference. Et de meme, une "indirection a semantique de pointeur" est une indirection qui se manipule comme un pointeur, sans forcement etre un pointeur. (Les pointeurs seront vu dans la partie sur la programmation orientée objet).
  
-Par exemple, les iterateurs que vous avez etudie avec les collections sont des indirections a semantique de pointeur, mais qui ne sont pas des pointeurs.+Par exemple, les itérateurs que vous avez etudie avec les collections sont des indirections a semantique de pointeur, mais qui ne sont pas des pointeurs.
 </note> </note>
  
-Les references sont des indirections qui permettent d'acceder directement a un objet. La syntaxe pour utiliser l'indirection est exactement la meme que la syntaxe pour utiliser l'objet. Dans certains cas, le compilateur peut meme remplacer l'indirection par un //alias de variable//, c'est a dire utiliser directement la variable referencee, mais avec un nom different. (Le code genere par le compilateur supprimera completement l'indirection).+Les références sont des indirections qui permettent d'acceder directement a un objet. La syntaxe pour utiliser l'indirection est exactement la meme que la syntaxe pour utiliser l'objet. Dans certains cas, le compilateur peut même remplacer l'indirection par un //alias de variable//, c'est a dire utiliser directement la variable référencée, mais avec un nom different. (Le code généré par le compilateur supprimera complètement l'indirection).
  
 <code cpp> <code cpp>
Ligne 219: Ligne 225:
 </code> </code>
  
-Les pointeurs sont des indirections qui ne permettent pas d'utiliser directement un objet. Pour acceder a l'objet, il faut d'abord appliquer une operation particuliere, appellee "dereferencement". "Dereferencer un pointeur" consiste donc a acceder a l'objet pointe par un pointeur.+Les pointeurs sont des indirections qui ne permettent pas d'utiliser directement un objet. Pour accéder a l'objet, il faut d'abord appliquer une opération particulière, appellee "déréférencement". "Déréférencer un pointeur" consiste donc a accéder a l'objet pointe par un pointeur.
  
 Par exemple, pour utiliser un iterateur, vous avez vu dans le chapitre [[iterateurs|]] qu'il fallait utiliser l'opérateur ''*'', place devant la variable. Par exemple, pour utiliser un iterateur, vous avez vu dans le chapitre [[iterateurs|]] qu'il fallait utiliser l'opérateur ''*'', place devant la variable.
Ligne 242: Ligne 248:
 Dans ce code, ''it'' est un itérateur (une indirection) sur le premier élément de la collection et ''*it'' (déréférencement de l'itérateur) correspond a ce premier élément (la valeur entière 12). Dans ce code, ''it'' est un itérateur (une indirection) sur le premier élément de la collection et ''*it'' (déréférencement de l'itérateur) correspond a ce premier élément (la valeur entière 12).
  
-<note>Habitudes+<note>
 Lorsque le nom d'une variable n'a pas d'importance (par exemple dans un code d'exemple ou pour des explications), il est classique de nommer une référence par ''ref'' et un pointeur par ''ptr'', ''p'' ou ''q''. Lorsque le nom d'une variable n'a pas d'importance (par exemple dans un code d'exemple ou pour des explications), il est classique de nommer une référence par ''ref'' et un pointeur par ''ptr'', ''p'' ou ''q''.
 </note> </note>
  
 <code cpp> <code cpp>
-// indirection a semantique de reference+// indirection a sémantique de référence
 ref = 123; ref = 123;
 std::cout << ref << std::endl; std::cout << ref << std::endl;
Ligne 256: Ligne 262:
 </code> </code>
  
-Les parenthèses ne sont pas forcement indispensables, l’opérateur de déréférencement ''*'' est prioritaire par rapport a l’opérateur d'affectation ''='' et l’opérateur de flux ''<<''. C'est a dire que le code ''*ptr = 123;'' sera interprété comme équivalent a ''(*ptr) = 123;'' (le pointeur est déréférencé PUIS une valeur est affectée a l'objet pointé) et non comme ''*(ptr = 123);'' (le pointeur est modifié PUIS il est déréférencé).+Les parenthèses ne sont pas forcément indispensables, l’opérateur de déréférencement ''*'' est prioritaire par rapport a l’opérateur d'affectation ''='' et l’opérateur de flux ''<<''. C'est a dire que le code ''*ptr = 123;'' sera interprété comme équivalent a ''(*ptr) = 123;'' (le pointeur est déréférencé PUIS une valeur est affectée a l'objet pointé) et non comme ''*(ptr = 123);'' (le pointeur est modifié PUIS il est déréférencé).
  
-La dernière syntaxe est valide, mais produira très probablement un crash. Manipuler les pointeurs de cette facon s'appelle l'arithmetique des pointeurs, c'est quelque chose de toujours dangereux a faire et ne donc etre realise que dans des cas tres particulier (interfacage avec le systeme ou le materiel ou d'autres langages).+La dernière syntaxe est valide, mais produira très probablement un crash. Manipuler les pointeurs de cette façon s'appelle l’arithmétique des pointeurs, c'est quelque chose de très complexe et ne donc être réalisé que dans des cas très particuliers (interfaçage avec le système, le matériel ou d'autres langages).
  
-Dans ce cours, pour eviter les ambiguites sur l'ordre d'evaluation des operateurs ou la confusion avec l'operateur de multiplication ''*'', les parentheses seront toujours utilisees.+Dans ce cours, pour éviter les ambiguïtés sur l'ordre d’évaluation des opérateurs ou la confusion avec l’opérateur de multiplication ''*'', les parenthèses seront toujours utilisées.
  
 +==== Validité des indirections ====
  
-## Validide des indirections +Vous avez vu dans le chapitre sur les itérateurs que ceux-ci pouvait être invalide. Quand c’est le cas, ils prennent une valeur particulière, correspondant a ''std::end()'', il faut donc toujours tester un itérateur avant utilisation.
- +
-Vous avez vu dans le chapitre sur les iterateurs que ceux-ci pouvait etre invalide. Quand c'etait le cas, ils prennait une valeur particuliere, correspondant a ''std::end()'', il faut donc toujours tester un iterateur avant utilisation.+
  
 <code cpp> <code cpp>
Ligne 272: Ligne 277:
 </code> </code>
  
-Vous avez egalement vu qu'un iterateur pouvait etre invalide, mais ne pas etre testable, par exemple apres que la collection soit modifiee.+Vous avez également vu qu'un itérateur pouvait être invalide, mais ne pas être testable, par exemple après que la collection soit modifiée.
  
 <code cpp> <code cpp>
Ligne 282: Ligne 287:
 </code> </code>
  
-Certaines operations sur certains types de collections peuvent invalider les iterateurs, et seule la documentation permet de savoir cela. En cas de doute, considerez que les iterateus sont invalides.+Certaines opérations peuvent invalider les itérateurs, et seule la documentation permet de savoir cela. En cas de doute, considérez que les itérateurs sont invalides.
  
-Mais en fait, ce probleme de validite n'est pas specifique aux iterateurs, mais a toutes les indirections. Certains types d'indirection (comme les references ou les pointeurs intelligents) permettent d'avoir des garanties plus fortes si elles sont correctement utilisses, mais il faut toujours rester vigilants.+Mais en fait, ce problème de validité n'est pas spécifique aux itérateurs, mais a toutes les indirections. Certains types d'indirection (comme les références ou les pointeurs intelligents) permettent d'avoir des garanties plus fortes si elles sont correctement utilisées, mais il faut toujours rester vigilants.
  
-L'equivalent de ''std::end()'' pour les pointeurs est ''nullptr'' ("pointeur nul"), il est donc possible de tester un pointeur avec ''assert''. Mais, comme pour les iterateurs, un pointeur peut etre invalide, mais ne pas etre testable ("dangling pointer").+L’équivalent de ''std::end()'' pour les pointeurs est ''nullptr'' ("pointeur nul"), il est donc possible de tester un pointeur avec ''assert''. Mais, comme pour les itérateurs, un pointeur peut être invalide, mais ne pas être testable ("dangling pointer").
  
-Apres avoir dereference une indirection a semantique de pointeur, vous obtenez encore une indirection, mais a semantique de reference cette fois ci. Cette nouvelle indirection peut etre conservee dans une variable, comme n'importe quelle indirection.+<code cpp> 
 +assert(ptr != nullptr); 
 +assert(ptr);  // équivalent 
 +</code> 
 + 
 +Apres avoir déréférencé une indirection a sémantique de pointeur, vous obtenez encore une indirection, mais a sémantique de référence cette fois ci. Cette nouvelle indirection peut être conservée dans une variable, comme n'importe quelle indirection.
  
 <code cpp> <code cpp>
Ligne 298: Ligne 308:
 </code> </code>
  
-Mais vous voyez dans ce code qu'il est facile d'invalider une reference. Si vous appeler ''clear'' apres avoir cree la reference, celle si sera invalide, tout comme l'iterateur.+Mais vous voyez dans ce code qu'il est facile d'invalider une référence. Si vous appelez ''clear'' après avoir créé la référence, celle-ci sera invalide, tout comme l'itérateur.
  
-Dans ces conditions, pourquoi une reference est plus sure qu'un pointeur ? La raison est qu'elle n'est pas destinee etre utilisee dans les memes conditions qu'un poitneur.+Dans ces conditions, pourquoi une référence est plus sûre qu'un pointeur ? La raison est qu'elle n'est pas destinée être utilisée dans les mêmes conditions qu'un pointeur.
  
-  * un pointeur est modifiable, il peut revecevoir une nouvelle valeur (affectation), etre nul, faire des operateurs arithmetiques dessus. Une reference est definie a l'initialisation et ne sera plus modifiable ensuite. +  * un pointeur est modifiable, il peut recevoir une nouvelle valeur (affectation), être nul, faire des opérateurs arithmétiques dessus. Une référence est définie a l'initialisation et ne sera plus modifiable ensuite. 
-  * un pointeur pourra etre transmis entre differentes fonctions et classes et avoir une duree de vie assez longue. Une reference ne sera pas conservee lorsque la duree de vie des objets n'est pas garantie.+  * un pointeur pourra être transmis entre differentes fonctions et classes et avoir une duree de vie assez longue. Une reference ne sera pas conservee lorsque la duree de vie des objets n'est pas garantie.
  
 La difference de securite entre pointeur et reference tient donc uniquement a leur utilisation. Les references imposent des contraintes plus fortes, mais offrent en retour des garanties plus fortes. Pour ameliorer la securite du code, il faut donc priviliger l'utilisateur des references, c'est a dire de concevoir au maximum son code pour rester dans les contraintes imposees par les references. (C'est pour cette raison que les pointeurs sont vu tres tardivement dans ce cours). La difference de securite entre pointeur et reference tient donc uniquement a leur utilisation. Les references imposent des contraintes plus fortes, mais offrent en retour des garanties plus fortes. Pour ameliorer la securite du code, il faut donc priviliger l'utilisateur des references, c'est a dire de concevoir au maximum son code pour rester dans les contraintes imposees par les references. (C'est pour cette raison que les pointeurs sont vu tres tardivement dans ce cours).
  
-<note>Cette approche est assez classique en C++. Pour resoudre un probleme, il est souvent possible d'utiliser plusieurs approches : des approches plus generiques (moins de contraintes, mais egalement moins de garanties) ou des approches plus specialisees (plus de contraintes, donc plus de garanties).+<note>Cette approche est assez classique en C++. Pour résoudre un probleme, il est souvent possible d'utiliser plusieurs approches : des approches plus génériques (moins de contraintes, mais également moins de garanties) ou des approches plus spécialisées (plus de contraintes, donc plus de garanties).
  
-En programmation moderne, la taille des projets est de plus en plus grande (ainsi que la complexite), la qualite du code est donc de plus en plus prioritaire. C'est pour cela que les langages modernes ont des bibliotheques standards de plus en plus importantes et le C++ n'echappe pas a cette regle : le langage C ne propose qu'un seul type d'indirection (les poitneurs), alors que le C++ en propose beaucoup plus (pointeurs du C, referencesiterateurs, pointeurs inteligents, etc.).+En programmation moderne, la taille des projets est de plus en plus grande (ainsi que la complexité), la qualité du code est donc de plus en plus prioritaire. C'est pour cela que les langages modernes ont des bibliothèques standards de plus en plus importantes et le C++ n’échappe pas a cette règle : le langage C ne propose qu'un seul type d'indirection (les pointeurs), alors que le C++ en propose beaucoup plus (pointeurs du C, référencesitérateurs, pointeurs intelligents, etc.).
  
-Cela pourrait laisser penser que les langages modernes sont plus complexes et plus long a apprendre (la norme C tient en 400 pages, la norme C++ en 1500 pages et la documentation du Java doit faire plusieurs milliers de pages), mais ca serait une vision fausse. Si vous souhaitez realiser une tache en particulier, vous aurez dans le premier cas peu de fonctionnaites utilisables (donc facile a apprendre), mais vous devrez tout faire vous meme, et donc avoir un code plus important et plus complexe (et plus de risque d'erreur). Dans le second cas, vous aurez plus d'outils a apprendre, mais ils seront plus puissants pour chaque tache. Le code sera donc plus simple et avec moins d'erreur.+Cela pourrait laisser penser que les langages modernes sont plus complexes et plus long a apprendre (la norme C tient en 400 pages, la norme C++ en 1500 pages et la documentation du Java doit faire plusieurs milliers de pages), mais ça serait une vision fausse. Si vous souhaitez réaliser une tâche en particulier, vous aurez dans le premier cas peu de fonctionnalités utilisables (donc facile a apprendre), mais vous devrez tout faire vous même, et donc avoir un code plus important et plus complexe (et plus de risque d'erreur). Dans le second cas, vous aurez plus d'outils a apprendre, mais ils seront plus puissants pour chaque tâche. Le code sera donc plus simple et avec moins d'erreur.
  
-Et en C++, vous devrez etendre cette approche a votre code. Si un outil n'est pas disponnible dans le langage ou une bibliothequeplutot que d'ecrire directement le code pour resoudre cette tache, il faudra faire en sorte de creer ces outils, de les rendre reutilisable et securise, puis de les utiliser pour resoudre votre probleme.+Et en C++, vous devrez étendre cette approche a votre code. Si un outil n'est pas disponible dans le langage ou une bibliothèqueplutôt que d’écrire directement le code pour résoudre cette tâche, il faudra faire en sorte de créer ces outils, de les rendre réutilisable et sécurisé, puis de les utiliser pour résoudre votre problème.
 </note> </note>
  
  
-## Les references comme parametre de fonction+==== Les références comme parametre de fonction ====
  
-Quelles sont les conditions pour que les references restent valides lors des appels de fonctions ?+Quelles sont les conditions pour que les références restent valides lors des appels de fonctions ?
  
-Le cas le plus simple est lorsque la reference est utilisee comme parametre de fonction.+Le cas le plus simple est lorsque la référence est utilisée comme paramètre de fonction.
  
 <code cpp main.cpp> <code cpp main.cpp>
Ligne 343: Ligne 353:
 </code> </code>
  
-Dans ce cas, vous avez la garantie que la fonction appellee ''f'' se terminera avant la fonction appelante ''main''. L'objet provenant de ''main'' sera donc forccement valide pendant toute la duree de l'execution de la fonction ''f'' et il n'y a aucun risque d'avoir une reference invalide. Et cela serait encore valide si la fonction ''f'' appellait une autre fonction, puis une autre fonction et ainsi de suite+Dans ce cas, vous avez la garantie que la fonction appelée ''f'' se terminera avant la fonction appelante ''main''. L'objet provenant de ''main'' sera donc forcément valide pendant toute la duree de l'exécution de la fonction ''f'' et il n'y a aucun risque d'avoir une référence invalide. Et cela serait encore valide si la fonction ''f'' appelait une autre fonction, puis une autre fonction et ainsi de suite.
- +
-Pour le retour de fonction, la situation est differente+
  
 +Pour le retour de fonction, la situation est différente. 
  
 <code cpp main.cpp> <code cpp main.cpp>
Ligne 362: Ligne 371:
 </code> </code>
  
-Dans ce code, la fonction ''f'' retourne une reference sur une variable locale a la fonction. Lorsque la fonction se termine (c'est a dire tout de suite apres que la reference soit creee, puisque la reference est le parametre de retour), la variable locale ''i'' est detruite et la reference devient invalide.+Dans ce code, la fonction ''f'' retourne une référence sur une variable locale a la fonction. Lorsque la fonction se termine (c'est a dire tout de suite après que la référence soit créée, puisque la référence est le paramètre de retour), la variable locale ''i'' est détruite et la référence devient invalide.
  
-**Ne JAMAIS retourner une reference sur une variable locale !**+**Ne JAMAIS retourner une référence sur une variable locale !**
  
-Notez bien que ce code ne produit pas d'erreur (et affichera peut etre la valeur correcte 123). Une reference etant consideree comme toujours valide, il n'y a pas de verification effectueee. Cela va produire un comportement indeterminee (//undefined behavior//), c'est de la responsabilite du developpeur de verifier son utilisation des references.+Notez bien que ce code ne produit pas d'erreur (et affichera peut être la valeur correcte "123"). Une référence étant considérée comme toujours valide, il n'y a pas de vérification effectuée. Cela va produire un comportement indéterminée (//undefined behavior//), c'est de la responsabilité du développeur de vérifier son utilisation des références.
  
-Une reference ne peut etre retournee par une fonction uniquement si elle correspond a un objet dont la duree de vie n'est pas limite par la fonction. Par exemple une reference quis erait passe en parametre.+Une référence ne peut être retournée par une fonction uniquement si elle correspond a un objet dont la durée de vie n'est pas limité par la fonction. Par exemple une référence qui serait passé en paramètre.
  
 <code cpp> <code cpp>
Ligne 377: Ligne 386:
 </code> </code>
  
-Dans tous les cas, faites bien attention quand vos objets sont creees et detruits et quand une indirection est valide ou non.+Dans tous les cas, faites bien attention quand vos objets sont créées et détruits et quand une indirection est valide ou non.
  
  
-## copie et deplacement d'indirections+==== Rvalue-reference ====
  
-Les indirections sont des types comme les autres et peuvent donc etre utilises comme n'importe quel type. Par exempleil est posible de creer des variables (comme deja vu:+Depuis le début de ce coursle terme de "valeur" (//value//est utilisé indifféremment pour désigner une littérale, une variable (constante ou non) ou une expression (un calcul, un retour de fonction, etc).
  
-<code cpp> +En fait, il existe différents types de valeurs, qui respectent des règles sémantiques relativement complexes. Il n'est pas nécessaire de toutes les connaître dans un premier temps, seules deux grandes catégories sont intéressantes au début. (Et les explications vont être simplifiées).
-int i { 123 }; +
-int & j { i }; +
-</code>+
  
-La seconde ligne de code peut se lire ''j'' est une variable, de type "reference sur un entier", qui contient comme valeur une indirection sur la variable ''i''.+  * les //lvalues// (//left-value//) : se sont les variables (constante ou non) ; 
 +  * les //rvalues// (//right-value//) ce sont tout le reste, c'est a dire les valeurs temporaires (les littérales ou les expressions).
  
-Une reference (obtenue directement ou apres deferencement d'un pointeur) peut se convertir implicitement en valeur, par copie. (Il faut donc que le type soit copiable).+Cette distinction va être importante pour le passages de valeurs dans les fonctions. Sans référence, c'est a dire lors d'un passage par valeur (copie), vous avez vu qu'il est possible d'appeler une fonction avec n'importe quel type de valeur.
  
 <code cpp> <code cpp>
-int 123 }+void f(int{}
-int & j { i }; +
-int & k { j }; +
-int l { j }; +
-</code>+
  
-Dans ce code, la variable ''j'' est une reference sur un entier (la variable ''i'', qui contient la valeur 123). La variable ''k'' est egalement une reference sur la variable ''i''. (Comme une reference peut etre vue comme un alias de variable, elle pourra etre supprimee par le compilateur. Et celui-ci sait tres bien que ''j'' est aussi une reference, il n'y a pas de raison que ''k'' passe par ''j'' pour referencer ''i'').+int main() { 
 +    int 123 }; 
 + f(i);    // ok avec une variable (lvalue) 
 +  f(123);  // ok avec une littérale (rvalue) 
 + f(12+34) // ok avec une expression (rvalue) 
 +
 +</code>
  
-Note : une consequence directe a cela est qu'il est possible d'avoir autant d'indirections que vous souhaitez sur un meme objet, il n'y a pas de limitation. +Avec les références constantes, la situation est identiquevous pouvez passer n'importe quel type de valeur.
- +
-Au contraire, la variable ''l'' n'est pas une referencemais contient un entier. C'est donc une copie de la valeur contenu dans la variable ''i'', et comme toujours lorsqu'il y a une copie, les objets sont independants et la modification d'une des deux variables ne sera pas reperctuee sur l'autre.+
  
 <code cpp> <code cpp>
-j++; +void f(int const&amp;) {}
-std::cout << i << std::endl; +
-l++; +
-std::cout << i << std::endl; +
-</code&gt;+
  
-affiche : +int main() { 
- +    int i { 123 }; 
-<;code>+ f(i)   // ok avec une variable (lvalue) 
-124 +  f(123) // ok avec une littérale (rvalue) 
-123+ f(12+34) // ok avec une expression (rvalue) 
 +}
 </code> </code>
  
-A noter qu'un objet complexe peut tout a fait contenir des indirections en interne. Dans ce cas, la copie de cet objet (si elle est autorisee) peut produire differents comportement, selon comment cette classe est concue. Par exemple, les classes ''std::string'' et ''std::shared_ptr'' (un type de pointeur intelligent) utilisent des pointeurs sur des objets internes. La copie d'un objet de type ''std::string'' copie l'integralite du tableau interne et les deux nouveaux objets sont independants (//deep copy//, copie en profondeur)Au contraire, la copie d'un objet de type ''std::shared_ptr'' ne copie pas l'objet interne +La situation change pour les références non constante. Celle-ci ne peuvent accepter qu'un seul type de valeur : les //lvalue//.
  
 +<code cpp>
 +void f(int &) {}
  
 +int main() {
 +    int i { 123 };
 + f(i);    // ok avec une variable (lvalue)
 +  f(123);  // erreur
 + f(12+34) // erreur
 +}
 +</code>
  
-Les indirections sont egalement utilisable avec l'inference de type. Dans le chapitre sur l'inferencevous avez vu une difference importante entre ''auto'' et ''decltype'' : le premier ne conserve pas les modificateurs de types, le second ouiLes references et la constance sont des exemples de modificateurs de type, il faudra donc faire attention +D'ailleurs, ce type de référence est parfois appelle //lvalue-reference//pour indiquer qu'elles n'acceptent que des //lvalue//.
  
 +Il existe en fait un second type de référence, qui n'acceptent que des //rvalue// et qui s'appelle donc //rvalue-reference//. (Lorsque le type de référence n'est pas précisé, il s'agit de //lvalue-reference//). Les //rvalue-reference// s’écrivent avec l’opérateur ''&&'' et ne sont jamais constantes.
  
 +<code cpp>
 +void f(int &&) {}
  
 +int main() {
 +    int i { 123 };
 + f(i);    // erreur
 +  f(123);  // ok avec une littérale (rvalue)
 + f(12+34) // ok avec une littérale (rvalue)
 +}
 +</code>
  
 +Pour résumer :
  
-inference de type+^  Passage              ^  lvalue   ^  rvalue   ^ 
 +|  Par valeur           |  oui      |  oui      | 
 +|  Référence constante  |  oui      |  oui      | 
 +|  Référence            |  oui      |  **non**  | 
 +|  Rvalue-reference     |  **non**  |  oui      |
  
 +<note>**Pourquoi deux types de référence ?**
  
-  * dans une collectionIl est possible de creer un tableau d'iterateur par exemple)+Les //lvalue// et les //rvalue// ont un comportement très différents lorsqu'elles sont utilisées dans une fonctionLa première continuera d'exister après l'appel de la fonction, il est donc possible de modifier la variable dans la fonction.
  
 +<code cpp>
 +void f(int & i) {
 +    i += 12;
 +}
  
-## Conversion d'une v+int main() { 
 +    int i { 123 }; 
 +    f(i); 
 +    std::cout << i << std::endl; 
 +
 +</code>
  
-conversion en valeur/copie+Au contraire, une //rvalue// (le résultat d'un calcul ou la valeur retournée par une fonction) existe que temporairement, elle n'est donc plus accessible après l'appel de la fonction. L'utilisation d'une //rvalue-reference// permet de dire au compilateur : "cette valeur ne sera plus utilisée dans la fonction appelante par la suite, tu peux donc optimiser comme tu veux l'utilisation de cette valeur".
  
-inference de type+C'est donc avant tout une question d'optimisation, qui sera surtout intéressant pour les classes complexes. Cela sera vu plus en détail dans la partie programmation orientée objet. Le plus souvent, vous pourrez utiliser les références (//lvalue//) non constante lorsque vous voulez modifier une variable dans une fonction et une (//lvalue//) référence constante dans les autres cas (ou un passage par valeur lorsque la copie est peu coûteuse, par exemple pour les types fondamentaux ''int'', ''double'', etc.) 
 +</note>
  
-correspondance arguement et parametre+Ces règles sont valides aussi pour les paramètres par défaut : 
  
-valeur par defaut+<code cpp> 
 +void f(int i = 0);        // ok 
 +void f(int const& i = 0)  // ok 
 +void f(int & i = 0);      // erreur (littérale vers lvalue-reference) 
 +</code>
  
 +<note>**std::move**
  
 +Pour être plus précis sur le rôle de la fonction ''std::move'', celle-ci ne "déplace" pas les objets ou n'autorise pas le compilateur a faire un déplacement. Elle convertie simplement une valeur en //rvalue-reference//. Le compilateur va donc interpréter cette valeur comme si c’était une valeur temporaire et pourra appliquer les optimisations compatibles avec un temporaire (déplacement ou copie le cas échéant).
 +</note>
  
 +Les differents types de références permet d'écrire des surcharges de fonctions. Cela sera vu dans le prochain chapitre.
  
  
 +==== Copie et déplacement d'indirections ====
  
 +Les indirections sont des types comme les autres et peuvent donc être utilisés comme n'importe quel type. Par exemple, il est possible de créer des variables (comme déjà vu) :
  
 +<code cpp>
 +int i { 123 };
 +int & j { i };
 +</code>
  
 +La seconde ligne de code peut se lire : ''j'' est une variable, de type "référence sur un entier", qui contient comme valeur une indirection sur la variable ''i''.
  
 +Une référence (obtenue directement ou après déréférencement d'un pointeur) peut se convertir implicitement en valeur, par copie. (Il faut donc que le type soit copiable).
  
 +<code cpp>
 +int i { 123 };
 +int & j { i };
 +int & k { j };
 +int l { j };
 +</code>
  
 +Dans ce code, la variable ''j'' est une référence sur un entier (la variable ''i'', qui contient la valeur 123). La variable ''k'' est également une référence sur la variable ''i''. (Comme une référence peut être vue comme un alias de variable, elle pourra être supprimée par le compilateur. Et celui-ci sait très bien que ''j'' est aussi une référence, il n'y a pas de raison que ''k'' passe par ''j'' pour référencer ''i'').
  
 +Note : une conséquence directe a cela est qu'il est possible d'avoir autant d'indirections que vous souhaitez sur un même objet, il n'y a pas de limitation.
  
 +Au contraire, la variable ''l'' n'est pas une référence, mais contient un entier. C'est donc une copie de la valeur contenu dans la variable ''i'', et comme toujours lorsqu'il y a une copie, les objets sont indépendants et la modification d'une des deux variables ne sera pas répercutée sur l'autre.
  
 +<code cpp>
 +j++;
 +std::cout << i << std::endl;
 +l++;
 +std::cout << i << std::endl;
 +</code>
  
 +affiche :
  
 +<code>
 +124
 +123
 +</code>
  
 +A noter qu'un objet complexe peut tout a fait contenir des indirections en interne. Dans ce cas, la copie de cet objet (si elle est autorisée) peut produire différents comportement, selon comment cette classe est conçue. Par exemple, les classes ''std::string'' et ''std::shared_ptr'' (un type de pointeur intelligent) utilisent des pointeurs sur des objets internes. La copie d'un objet de type ''std::string'' copie l’intégralité du tableau interne et l'objet copié est indépendant du premier objet (//deep copy//, copie en profondeur). Au contraire, la copie d'un objet de type ''std::shared_ptr'' ne copie pas l'objet interne et la copie pointe sur le même objet que le pointeur original (//shallow copy//, copie superficielle).
  
  
 +==== Inférence de type ====
  
 +Les indirections sont également utilisable avec l’inférence de type. Dans le chapitre sur l’inférence, vous avez vu une différence importante entre ''auto'' et ''decltype'' : le premier ne conserve pas les modificateurs de types, le second oui. Les références et la constance sont des exemples de modificateurs de type, il faudra donc faire attention a ajouter explicitement une référence si vous utilisez ''auto'' si nécessaire.
  
 +<code cpp>
 +int i { 123 };
 +int & ref { i };
  
 +auto a { ref };           // auto sera déduit en int
 +decltype(auto) b { ref }; // decltype(auto) sera déduit en int&
 +decltype(ref) c { i };    // decltype(ref) sera déduit en int&
  
 +auto & d { ref };         // auto sera déduit en int, donc le type sera int &
 +</code>
  
 +Cela est également applicable aux types de retour de fonction.
  
 +Il peut être perturbant d'avoir plusieurs mot-clés différents pour l’inférence de type, mais cela permet d'avoir plus de liberté dans les codes. Il faut retenir :
  
 +  * ''auto'' lorsque vous souhaitez explicitement une valeur (par copie) ;
 +  * ''auto &'' (constante ou non) lorsque vous souhaites explicitement une référence ;
 +  * ''decltype'' lorsque vous souhaitez le type exacte 
  
 +<code cpp>
 +int f();
 +int & g();
  
 +// force une valeur
 +auto value1 = f();
 +auto value2 = g();
  
 +// force une référence
 +auto & ref1 = f();
 +auto & ref2 = g();
  
 +// ne force pas
 +decltype(auto) rv1 = f();  // valeur
 +decltype(auto) rv2 = g();  // référence
 +</code>
  
  
 +^ [[parametres_arguments|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[surcharge_fonctions|Chapitre suivant]] ^
  
- 
- 
- 
- 
- 
- 
- 
- 
- 
- 
- 
-=== Initialisation et affectation === 
- 
-non affectable: reference 
- 
-affectable: pointeur, iterateru, reference_wrapper 
- 
-Les indirections sont un type d'objet et peuvent donc etre manipule comme un objet : copie, deplacmeent. 
- 
- 
-=== Proprietes des objets === 
- 
-ownership: shared/unique 
- 
-sans ownership: reference, iterteur 
- 
- 
-==== Compatiblite ==== 
- 
-par defaut, arguement accepte 
- 
- 
-auto, declrtype 
- 
- 
-^ [[parametres_arguments|Chapitre précédent]] ^ [[programmez_avec_le_langage_c|Sommaire principal]] ^ [[surcharge_fonctions|Chapitre suivant]] ^ 
references.1468061414.txt.gz · Dernière modification: 2016/07/09 12:50 par gbdivers