Le but de cet article est de rappeler quelques éléments de syntaxe sur les pointeurs et références en C++.
Référence managée (en C++/CLI)
Pointeur et réference dans les appels de fonctions
Arguments de fonctions
Retour de fonction
On considère la classe suivante:
Point.h | Point.cpp |
---|---|
|
|
Déclaration par valeur
- Les objets déclarés de cette façon ont une existence liée au scope dans lequel ils sont déclarés. Par exemple, un objet déclaré "par valeur" dans une fonction aura une existence liée au corps de la fonction. Si on sort de cette fonction, l'objet sera détruit implicitement, il n'est pas nécessaire d'implémenter une instruction pour détruire l'objet.
- Les affectations de ces objets effectuent des copies "par valeur" c'est-à-dire qu'en absence de constructeur de copie, l'objet est copié intégralement c'est-à-dire que la copie de l'objet est effectuée membre par membre.
- La plupart du temps, les objets de type primitif (
int, bool, char, float
etc...) sont déclarés de cette façon. - Les copies d'objets déclarés "par valeur" peuvent représenter un coût non négligeable en performance si les objets sont de grande taille.
Quelques exemples:
Syntaxe | Remarques | |
---|---|---|
Déclaration + initialisation (sans valeur d'initialisation) |
|
|
Déclaration + initialisation (avec valeur d'initialisation) |
ou
ou
ou
|
Implique l'existence du constructeur suivant:
Dans le cas de la syntaxe suivante:
|
ou
|
Le constructeur de copie ne doit pas être obligatoirement déclaré pour écrire cette ligne. Il sera rajouté implicitement par le compilateur. | |
Copie |
|
|
Accès aux membres avec '.' |
|
Référence
- Une variable contenant une référence d'un objet peut être considérée comme un alias de cet objet c'est-à-dire que toutes les modifications effectuées sur la référence impacte l'objet lui-même.
- Utiliser des références permet d'éviter des copies d'objets "par valeur" lors des passages d'arguments ou des affectations.
- Il n'existe pas de référence nulle.
- Il existe 2 types de références (voir les différences entre Lvalue et Rvalue):
- Référence Lvalue (i.e. Lvalue reference): ce sont les références usuelles déclarées avec un caractère '
&
'. Les références Lvalue permettent de référencer les arguments Lvalue d'une expression. - Référence Rvalue (i.e. Rvalue reference): les références Rvalue permettent de référencer les arguments Rvalue d'une expression. Ces références sont indiquées avec les caractères '
&&
'. Il s'agit d'une optimisation pour améliorer les performances dans le cas de manipulations d'objet déclaré "par valeur" de façon à éviter d'effectuer des copies "par valeur" lors des affectations.
- Référence Lvalue (i.e. Lvalue reference): ce sont les références usuelles déclarées avec un caractère '
- Les références conviennent pour les données membres d'une classe quand l'instance de ce membre existe pendant toute la durée de vie de la classe.
Dans une expression, un argument Lvalue (i.e. left-value) correspond à un objet possédant un nom. Une variable possède un nom et permet de désigner un objet en mémoire.
Par exemple:
int x = 1; // x est une Lvalue
Lvalue sous-entends qu'un argument de ce type est à gauche de l'opérateur d'affectation ce qui n'est pas forcément le cas. Une Lvalue peut apparaître à gauche ou à droite de l'opérateur d'affectation.
Par exemple, si on écrit:
int a = 1;
int b = 2;
a = b;
b = a;
a
et b
sont à la fois Lvalue et Rvalue.
Dans une expression, un argument Rvalue (i.e. right-value) correspond à une valeur temporaire qui n'est pas persistée ou accessible en utilisant une variable.
Par exemple:
int i = 2; // 2 est une Rvalue
int GetValue()
{
//
}
int i = GetValue();
GetValue()
est une Rvalue par contre i
est une Lvalue.
Une Rvalue sert à affecter une valeur à une variable, elle est temporaire car il n'existe pas de nom pour y faire référence. Une Rvalue est toujours située à droite de l'opérateur d'affectation.
Par exemple, si on écrit le code suivant:
int a = 1;
int b = 2;
a + b = 3; // ERREUR
L'affectation n'est pas possible car le résultat de l'opération a + b
est une Rvalue. En effet, l'opération ne fait pas référence à une variable, il s'agit d'une valeur temporaire. Une Rvalue doit toujours être à droite de l'opérateur d'affectation.
Quelques exemples:
Syntaxe | Remarques | |
---|---|---|
Déclaration + initialisation (sans paramètres) |
ou
ou
|
|
Déclaration + initialisation (avec paramètres) |
ou
|
Un constructeur par défaut est nécessaire pour écrire la ligne: Point newPoint; |
ou
|
Référence constante. | |
|
Il n'est pas possible d'initialiser une référence de cette façon sans utiliser const . |
|
Initialisation obligatoire |
|
ATTENTION: entraîne une erreur de compilation. |
Référence Lvalue (i.e. Lvalue reference) |
|
Entraîne une erreur de compilation car Point(1, 2) est une Rvalue et la référence déclarée avec Point &newPoint est une référence Lvalue. |
|
Cette ligne n'entraîne pas d'erreurs. Une autre solution pourrait être d'utiliser une référence Rvalue (sans utiliser const ). |
|
Accès aux membres de l'objet référencé avec '.' |
|
L'accès aux membres de l'objet se fait avec '.'. |
La modification de la référence modifie l'objet |
|
L'affichage est:6 . |
Les modifications de l'objet sont visibles pour les autres références |
|
L'affichage est:6 . |
Ré-affectation d'une référence |
|
refPoint référence désormais secondPoint . |
Pas de référence nulle |
|
Impossible, la référence nulle n'existe pas. |
Opérateur "address-of": '&' permet d'obtenir un pointeur vers l'objet |
|
L'opérateur '&' ne peut être utilisé qu'avec la partie droite de l'opérateur '=' (appelé RHS pour Right Hand Side).
ATTENTION: |
Référence constante |
|
La ligne pointRef.a = 5 entraîne une erreur de compilation car la référence est constante. |
|
Les modifications sur la référence ne sont pas possibles. | |
est équivalent à:
|
Ces 2 lignes sont équivalentes. | |
|
const est ignoré par le compilateur dans ce cas. |
|
Référence Rvalue | L'utilisation de référence Rvalue est une optimisation permettant de transferer un objet dynamiquement alloué en mémoire d'un objet à l'autre sans avoir à l'affecter à une variable. Par exemple, il n'est pas possible d'écrire la ligne suivante sans const car Point(1, 2) est une Rvalue et pointRef est une référence Lvalue:
On peut toutefois utiliser une référence Rvalue:
Move Semantics
On peut appeler cette fonction directement avec une Rvalue sans avoir à affecter l'argument à une variable au préalable:
Sans référence Rvalue, il aurait fallu effectuer une affectation au préalable:
Perfect forwarding Par exemple, sans référence Rvalue, il faut définir les surcharges suivantes...:
...pour être capable d'effectuer les 2 appels suivants:
Avec une réference Rvalue, une seule fonction suffit:
Pour tirer complétement partie de la fonctionnalité Move Semantics, il faut définir une constructeur de déplacement (i.e. Move Constructor) et éventuellement définir une surcharge à l'opérateur d'affectation:
L'implémentation du constructeur et de la surcharge de l'opérateur doit respecter certaines règles pour être efficace (voir Move Constructors and Move Assignment Operators (C++) pour plus de détails). |
Pointeur
Quelques remarques générales sur les pointeurs:
- Une variable de type pointeur contient une adresse permettant de pointer avec un typage fort vers un objet en mémoire.
- Les objets pointés par une variable de type pointeur peuvent être alloués sur la pile ou dans le tas suivant comment ils ont été déclarés. Si on initialise ces objets avec l'opérateur
new
, ils sont alloués dans le tas. Toutefois il est possible d'obtenir un pointeur vers un objet alloué sur la pile avec l'opérateur '&
'. - Quand un objet est instancié avec
new
, il doit être libéré en utilisantdelete
. - Les pointeurs conviennent en tant que données membres d'une classe lorsqu'il sera nécessaire de libérer l'instance de l'objet pointé lors de la durée de vie de la classe.
Quelques exemples:
Syntaxe | Remarques | |
---|---|---|
Déclaration |
ou
ou
|
Ces 3 notations sont équivalentes. |
Déclaration + initialisation |
|
|
Accès aux membres de l'objet avec l'opérateur '-> ' |
|
|
Pointeur nul |
|
|
Initialisation à partir d'une référence en utilisant l'opérateur de déférencement ' & ' |
|
L'opérateur '& ' ne peut être utilisé que pour la partie droite de l'opérateur d'affectation (appelé RHS pour Right Hand Side). |
Obtenir l'objet pointé avec l'opérateur '* ' etaffecter une copie de l'objet pointé |
|
L'opérateur '* ' peut être utilisé dans la partie droite de l'opérateur d'affectation (RHS pour Right Hand Side).
ATTENTION: |
Obtenir une référence d'un objet à partir d'un pointeur avec l'opérateur ' * ' |
|
On affecte une variable contenant une référence de l'objet. |
Le pointeur possède un typage fort |
|
ATTENTION: impossible, cette ligne entraîne une erreur de compilation car les types sont différents |
Ré-affectation d'un pointeur |
|
Le pointeur est copié "par valeur" toutefois les 2 pointeurs pointent vers le même objet. |
Ré-affectation de l'objet pointé |
|
|
Utilisation d'un pointeur non typé void * |
|
untypedPointer est un pointeur non typé. |
Suppression d'un objet alloué |
|
Avec l'opérateur new , l'objet est alloué dans le tas, il faut penser à le supprimer après utilisation pour éviter une fuite mémoire |
Un pointeur vers un pointeur |
|
Le résultat est:5 |
Valeur pointée constante |
|
On ne peut pas modifier l'objet pointé, donc cette ligne provoque une erreur de compilation. |
|
On peut modifier la valeur du pointeur. | |
est équivalent à:
|
||
Pointeur constant |
|
L'objet pointé peut être modifié. |
|
Le pointeur est constant et ne peut pas être modifié. |
Référence managée (en C++/CLI)
- Une référence managée est semblable aux références en C#.
- Les objets déclarées sous forme de références managées sont instanciés dans le tas managé. Leur durée de vie est géré pour la Garbage collector.
- Il n'est pas nécessaire de libérer les objets déclarés en tant que référence managée.
- Ce code n'est valable que si l'exécutable supporte du code CLR. Les références managées sont aussi appelées handle.
On considère la classe Line
telle que:
Line.h | Line.cpp |
---|---|
|
|
Quelques exemples:
Syntaxe | Remarques | |
---|---|---|
Déclaration |
ou
ou
|
Les 3 notations sont équivalentes. |
Déclaration + initialisation |
|
|
Accès aux membres de l'objet avec l'opérateur '-> ' |
|
|
Pointeur nul |
|
|
Suppression d'une instance |
|
|
Pointeur et réference dans les appels de fonctions
Arguments de fonctions
Quelques exemples de passage d'arguments à une fonction:
Syntaxe | Remarques | |
---|---|---|
Copie par valeur | Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:1 2
L'argument est passé par valeur, une copie est effectuée lors de l'appel. |
Copie par référence | Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:2 4
L'argument est passé par référence. |
Utilisation d'un pointeur | Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:2 4
L'argument contient un pointeur. |
Copie d'une référence managée (En C++/CLI) |
Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:2 4 6 8
L'argument est passé par référence. |
Retour de fonction
Syntaxe | Remarques | |
---|---|---|
Par valeur | Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:1 2
Une copie de l'objet créée dans la fonction, est retournée. |
Par référence (MAUVAISE IMPLEMENTATION) |
Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:1935459609 -1
ATTENTION: ne pas retourner une référence vers un objet créé dans la fonction. Dans cet exemple, |
Par référence (A EVITER) |
Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:1 2
Un objet est créé et alloué sur le tas dans le corps de la fonction. On retourne une référence vers cet objet. |
Par pointeur | Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:1 2
Ne pas oublier de supprimer l'objet après utilisation. |
Par référence managée | Déclaration de la fonction:
Syntaxe de l'appel:
|
Le résultat est:1 2 3 4
L'objet est créé dans le tas managé, il n'y a pas de nécessité de le supprimer. |
Membres d'une classe
Quelques exemples de déclarations d'objets membres d'une classe:
Syntaxe | Remarques | |
---|---|---|
Par valeur (A éviter dans le cas d'objet complexe) |
Définition .h:
Définition .cpp:
|
|
Par référence | Définition .h:
Définition .cpp:
|
|
Par référence (avec const ) |
Définition .h:
Définition .cpp:
|
|
Par pointeur | Définition .h:
Définition .cpp:
|
|
Par référence managée (classe managée) |
Définition .h:
Définition .cpp:
|
Le membre de type managée doit se trouver dans une classe managée. Le comportement est le même qu'en C#. |
Comparaison d'objets
Pour être capable de comparer des objets complexes, il faut surcharger l'opérateur d'égalité.
Par exemple:
bool operator==(const Point &pointA, const Point &pointB)
{
return pointA.a == pointB.a && pointA.b == pointB.b;
};
Si les membres d'une classe sont privés, on doit ajouter la déclaration suivante dans le fichier .h
de la classe pour que la surchage de l'opérateur puisse accèder à ces membres:
friend bool operator==(const Point &pointA, const Point &pointB);
L'implémentation de la surcharge de l'opérateur indiquée plus haut suffit à effectuer les comparaisons suivantes:
Syntaxe | Remarques | |
---|---|---|
Comparaison d'objets définis "par valeur" |
|
Le résultat est:OK
On peut aussi surcharger l'opérateur de cette façon:
|
Comparaison de références |
|
Le résultat est:OK
Si on surcharge l'opérateur de cette façon (sans
On obtiendra une erreur de compilation. |
Comparaison de pointeurs |
|
Le résultat est:OK
Il n'est pas possible de déclarer la surcharge suivante:
Ce type de surcharge pourrait préter à confusion dans le cas où on compare des pointeurs directement:
|
- C++ Programming Language - Pointers, References and Dynamic Memory Allocation: https://www.ntu.edu.sg/home/ehchua/programming/cpp/cp4_PointerReference.html
- Should I prefer pointers or references in member data?: https://stackoverflow.com/questions/892133/should-i-prefer-pointers-or-references-in-member-data
- Pointer declaration: https://en.cppreference.com/w/cpp/language/pointer
- Classes with Pointer Data Members: http://pages.cs.wisc.edu/~hasti/cs368/CppTutorial/NOTES/CLASSES-PTRS.html
- C++ Rvalue References Explained : http://thbecker.net/articles/rvalue_references/section_01.html
- Reference of Reference in C++: https://stackoverflow.com/questions/5590978/reference-of-reference-in-c
- Copy constructors, assignment operators, and exception safe assignment: http://www.cplusplus.com/articles/y8hv0pDG/
- OOP: Copy Constructors: http://www.fredosaurus.com/notes-cpp/oop-condestructors/copyconstructors.html
- Lvalues and Rvalues (C++): https://docs.microsoft.com/en-us/cpp/cpp/lvalues-and-rvalues-visual-cpp?view=vs-2019
- Lvalue Reference Declarator: &: https://docs.microsoft.com/en-us/cpp/cpp/lvalue-reference-declarator-amp?view=vs-2019
- Rvalue Reference Declarator: &&: https://docs.microsoft.com/en-us/cpp/cpp/rvalue-reference-declarator-amp-amp?view=vs-2019
- The Notion of Lvalues and Rvalues: https://www.codeproject.com/articles/313469/the-notion-of-lvalues-and-rvalues
- Rvalue Member Functions and Overloading: https://www.codeproject.com/Tips/1251441/Rvalue-member-functions-and-overloading
- What is move semantics?: https://stackoverflow.com/questions/3106110/what-is-move-semantics
- The new C++ 11 rvalue reference && and why you should start using it: https://www.codeproject.com/Articles/453022/The-new-Cplusplus-11-rvalue-reference-and-why-you