Aide-mémoire sur les pointeurs et références en C++

Le but de cet article est de rappeler quelques éléments de syntaxe sur les pointeurs et références en C++.

On considère la classe suivante:

Point.h Point.cpp
class Point
{
public:
    Point();
    Point(int a, int b);
    Point(const Point &copy);
    ~Point();

    int a;
    int b;
};

Point::Point()
{}

Point::Point(int a, int b)
{
  this->a = a;
  this->b = b;
}

Point::Point(const Point &copy)
{
  this->a = copy.a;
  this->b = copy.b;
}

Point::~Point()
{
}

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)
Point newPoint;
  • Il n'est pas possible d'effectuer seulement une déclaration. Cette syntaxe n'effectue pas seulement une déclaration, elle permet d'exécuter le constructeur par défaut.
  • La classe doit comporter un constructeur par défaut:
    Point::Point()
    {}
Déclaration + initialisation
(avec valeur d'initialisation)
Point newPoint(2, 4);

ou

Point newPoint = Point(2, 4);

ou

Point newPoint{2, 4};

ou

Point newPoint = {2, 4};
Implique l'existence du constructeur suivant:

Point::Point(int a, int b)
{
  this->a = a;
  this->b = b;
}

Dans le cas de la syntaxe suivante:
Point newPoint = Point(2, 4);
2 opérations sont effectuées même si le compilateur les optimise pour n'en faire qu'une seule:

  • L'instanciation d'un objet en utilisant le constructeur précédent et
  • L'instanciation d'un autre objet en utilisant le constructeur de copie (si ce constructeur n'existe pas explicitement, il est rajouté par le compilateur).
Point firstPoint(2, 4);
Point newPoint(firstPoint);

ou

Point firstPoint(2, 4);
Point newPoint{firstPoint};
Le constructeur de copie ne doit pas être obligatoirement déclaré pour écrire cette ligne. Il sera rajouté implicitement par le compilateur.
Copie
Point newPoint(2, 4);
Point otherPoint = newPoint;
  • Cette ligne effectue une copie par valeur, newPoint et otherPoint sont des objets différents.
  • Implique un constructeur de copie (copy constructor):
    Point::Point(const Point &point)
    {
      this->a = point.a;
      this->b = point.b;
    }
    

    Ce constructeur est créé par le compilateur s'il n'existe pas.

Accès aux membres avec '.'
Point newPoint(2, 4);
cout << newPoint.a << "\n";

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.
  • 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.
Différences entre Lvalue et Rvalue

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)
Point &newPoint();

ou

Point & newPoint();

ou

Point& newPoint();
  • Toutes ces notations sont équivalentes.
  • Il n'est pas possible d'effectuer seulement une déclaration d'une référence. Ce code n'effectue pas seulement une déclaration comme on pourrait le penser mais il exécute aussi le constructeur par défaut.
  • Chacune de ces lignes implique un constructeur par défaut.
Déclaration + initialisation
(avec paramètres)
Point newPoint;
Point &pointRef = newPoint;

ou

Point newPoint;
Point &pointRef(newPoint);
Un constructeur par défaut est nécessaire pour écrire la ligne:
Point newPoint;
const Point &pointRef = Point(2, 4);

ou

const Point &pointRef(Point(2, 4));
Référence constante.
Point &newPoint(2, 4);
Il n'est pas possible d'initialiser une référence de cette façon sans utiliser const.
Initialisation obligatoire
Point &newPoint; // ERREUR
ATTENTION: entraîne une erreur de compilation.
Référence Lvalue (i.e. Lvalue reference)
Point &newPoint = Point(1, 2);
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.
const Point &newPoint = Point(1, 2);
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 '.'
Point newPoint(2, 4);
Point &pointRef = newPoint;
newPoint.a = 6;
newPoint.b = 9;
L'accès aux membres de l'objet se fait avec '.'.
La modification de la référence modifie l'objet
Point newPoint(1, 2);
Point &pointRef = newPoint;
pointRef.a = 6;
cout << newPoint.a << "\n";
L'affichage est:
6.
Les modifications de l'objet sont visibles pour les autres références
Point newPoint(1, 2);
Point &firstRef = newPoint;
Point &secondRef = newPoint;
firstRef.a = 6;
cout << secondRef.a << "\n";
L'affichage est:
6.
Ré-affectation d'une référence
Point firstPoint(1, 2);
Point secondPoint(3, 4);
Point &refPoint = firstPoint;
refPoint = secondPoint;
refPoint référence désormais secondPoint.
Pas de référence nulle
Point &newPoint = NULL; // ERREUR
Impossible, la référence nulle n'existe pas.
Opérateur "address-of":
'&' permet d'obtenir un pointeur vers l'objet
Point firstPoint(1, 2);
Point &pointRef = firstPoint;
Point *pointer = &pointRef;
L'opérateur '&' ne peut être utilisé qu'avec la partie droite de l'opérateur '=' (appelé RHS pour Right Hand Side).

ATTENTION:
Le pointeur reste lié à l'objet d'origine. Si cet objet est détruit dans le cas où l'exécution se fait en dehors du scope de la déclaration de la variable firstPoint, le pointeur sera corrompu.

Référence constante
const Point &pointRef = Point(1, 2);
pointRef.a = 5; // ERREUR
La ligne pointRef.a = 5 entraîne une erreur de compilation car la référence est constante.
const Point &pointRef = Point(1, 2);
Point otherPoint(2, 4);
pointRef = otherPoint; // ERREUR
Les modifications sur la référence ne sont pas possibles.
const Point &pointRef = Point(1, 2);

est équivalent à:

Point const &pointRef = Point(1, 2);
Ces 2 lignes sont équivalentes.
Point point(1, 2);
Point & const pointRef = point;
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:

const Point &pointRef = Point(1, 2);  // OK
Point &pointRef = Point(1, 2);  // ERREUR

On peut toutefois utiliser une référence Rvalue:

Point &&pointRef = Point(1, 2);  // OK

Move Semantics
L'intérêt de cette fonctionnalité est de permettre de déplacer directement des objets alloués dynamiquement sans avoir à effectuer de nouvelles allocations et sans effectuer de copies en mémoire (cette technique est appelée Move Semantics).
Par exemple, si on définit la fonction:

void UsePoint(Point &&point)
{
    // ...
}

On peut appeler cette fonction directement avec une Rvalue sans avoir à affecter l'argument à une variable au préalable:

UsePoint(Point(1, 2));

Sans référence Rvalue, il aurait fallu effectuer une affectation au préalable:

void UsePoint(Point &point)
{
    // ...
}

Point point(1, 2); // Affectation de la variable point
UsePoint(point);

Perfect forwarding
L'utilisation de références Rvalue permet de définir moins de surcharges de fonctions. Au lieu de déclarer des surcharges avec T& et const T&, on peut directement utiliser une référence Rvalue T&&.

Par exemple, sans référence Rvalue, il faut définir les surcharges suivantes...:

void UsePoint(Point &point)
{
    // ...
}

void UsePoint(const Point &point)
{
    // ...
}

...pour être capable d'effectuer les 2 appels suivants:

Point point(1, 2);
UsePoint(point); // cet appel utilise la surcharge avec Point &point.
UsePoint(Point(3, 4)); // cet appel utilise la surcharge avec const Point &point.

Avec une réference Rvalue, une seule fonction suffit:

void UsePoint(Point &&point)
{
    // ...
}

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:

// Constructeur de déplacement
Point(Point &&otherPoint)
{
    // ...
}

// Surchage de l'opérateur d'affectation
Point &operator=(Point &&otherPoint)
{
    // ...
}

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 utilisant delete.
  • 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
Point* newPoint = NULL;

ou

Point * newPoint = NULL;

ou

Point *newPoint = NULL;
Ces 3 notations sont équivalentes.
Déclaration + initialisation
Point *newPoint = new Point(1, 2);
Accès aux membres de l'objet avec l'opérateur '->'
Point *newPoint = new Point(1, 2);
cout << newPoint->a < "\n";
cout << newPoint->b < "\n";
Pointeur nul
Point *newPoint = NULL;
Initialisation à partir d'une référence en utilisant
l'opérateur de déférencement '&'
Point &newPoint(1, 2);
Point *pointer = &newPoint;
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 '*' et
affecter une copie de l'objet pointé
Point *pointer = new Point(1,3);
Point objectCopy = *pointer;

objectCopy.a = 6;
std::cout << pointer->a << "\n"; // le résultat est 1
L'opérateur '*' peut être utilisé dans la partie droite de l'opérateur d'affectation (RHS pour Right Hand Side).

ATTENTION: objectCopy est une copie de l'objet pointé par pointer.

Obtenir une référence d'un objet à partir d'un pointeur
avec l'opérateur '*'
Point *pointer = newPoint(1,3);
Point &pointRef = *pointer;

pointer->a = 6;
std::cout << pointer->a << "\n"; // Le résultat est 6
On affecte une variable contenant une référence de l'objet.
Le pointeur possède un typage fort
Point *pointer = new Point(1, 3);
Line *newLine = newPoint; // ERREUR
ATTENTION: impossible, cette ligne entraîne une erreur de compilation car les types sont différents
Ré-affectation d'un pointeur
Point *firstPoint = new Point(1, 3);
Point *secondPoint = new Point(3, 6);
secondPoint = firstPoint;
Le pointeur est copié "par valeur" toutefois les 2 pointeurs pointent vers le même objet.
Ré-affectation de l'objet pointé
Point *firstPoint = new Point(3, 6);
Point newPoint(1, 2);
*firstPoint = newPoint;

firstPoint->a = 9;
cout << newPoint->a << "\n";
  • Le résultat est 1.
  • A la ligne:
    *firstPoint = newPoint;
    newPoint est copié "par valeur" dans l'objet pointé par firstPoint.
  • newPoint n'est pas modifié à la ligne:
    firstPoint->a = 9;
  • L'opérateur '*' peut être utilisé dans la partie gauche de l'opération d'affectation (appelé LHS pour Left Hand Side).
Utilisation d'un pointeur non typé void *
Point newPoint(1, 2);
Point *pointer = &newPoint;
void *untypedPointer = pointer;

Point *otherPoint = static_cast<Point *>(untypedPointer);
untypedPointer est un pointeur non typé.
Suppression d'un objet alloué
Point *pointer = new Point(1, 3);
delete pointer;
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
Point *pointer = new Point(1, 2);
Point **pointerToPointer = &pointer;
(*pointerToPointer)->a = 5;
cout << pointer->a << "\n";
Le résultat est:
5
Valeur pointée constante
const Point *pointA = new Point(1, 2);

pointA->a = 5; // ERREUR
On ne peut pas modifier l'objet pointé, donc cette ligne provoque une erreur de compilation.
const Point *pointA = new Point(1, 2);
Point *pointB = new Point(3, 4);
pointA = pointB;
On peut modifier la valeur du pointeur.
const Point *pointA = new Point(1, 2);

est équivalent à:

Point const *pointA = new Point(1, 2);
Pointeur constant
Point * const pointA = new Point(1, 2);
pointA->a = 5;
L'objet pointé peut être modifié.
Point * const pointA = new Point(1, 2);
Point *pointB = new Point(3, 4);
pointA = pointB; // ERREUR
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
public ref class Line
{
public:
    Line();
    Line(int x1, int y1, int x2, int y2);

    int x1;
    int y1;
    int x2;
    int y2;
};

Line::Line()
{}

Line::Line(int x1, int y1, int x2, int y2)
{
    this->x1 = x1;
    this->y1 = y1;
    this->x2 = x2;
    this->y1 = y1;
}

Quelques exemples:

Syntaxe Remarques
Déclaration
Line^ line;

ou

Line ^ line;

ou

Line ^line;
Les 3 notations sont équivalentes.
Déclaration + initialisation
Line ^line = gcnew Line(1, 2, 3, 6);
Accès aux membres de l'objet avec l'opérateur '->'
Line ^line = gcnew Line(1, 2, 3, 6);
cout << line->x1 << "\n";
cout << line->x2 << "\n";
Pointeur nul
Line ^line = nullptr;
Suppression d'une instance
Line ^line = gcnew Line(1, 2, 3, 6);
delete line;
  • line est alloué sur le tas managé géré par le Garbage Collector donc il n'y a pas de risque de fuite mémoire.
  • delete line sert seulement pour exécuter le destructeur de line volontairement. C'est l'équivalent du pattern IDisposable en C#.

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:

void MovePoint(Point point, 
  int newA, int newB)
{
  point.a = newA;
  point.b = newB;
}

Syntaxe de l'appel:

Point newPoint(1, 2);
Point::MovePoint(newPoint, 2, 4);
std::cout << newPoint.a << "\n";
std::cout << newPoint.b << "\n";
Le résultat est:
1
2

L'argument est passé par valeur, une copie est effectuée lors de l'appel.
Si on modifie l'objet à l'intérieur de la fonction, on modifie la copie et non l'original.
Cet appel implique un constructeur de copie (copy constructor).

Copie par référence Déclaration de la fonction:

void MovePoint(Point &point, 
  int newA, int newB)
{
  point.a = newA;
  point.b = newB;
}

Syntaxe de l'appel:

Point newPoint(1, 2);
Point::MovePoint(newPoint, 2, 4);
std::cout << newPoint.a << "\n";
std::cout << newPoint.b << "\n";
Le résultat est:
2
4

L'argument est passé par référence.
Si on modifie l'objet en utilisant la référence à l'intérieur de la fonction, on modifie l'original.

Utilisation d'un pointeur Déclaration de la fonction:

void MovePoint(Point *point, 
  int newA, int newB)
{
  point->a = newA;
  point->b = newB;
}

Syntaxe de l'appel:

Point newPoint(1, 2);
Point::MovePoint(&newPoint, 2, 4);
std::cout << newPoint.a << "\n";
std::cout << newPoint.b << "\n";
Le résultat est:
2
4

L'argument contient un pointeur.
Si on modifie l'objet dans la fonction, on modifie l'original.

Copie d'une référence managée
(En C++/CLI)
Déclaration de la fonction:

void MoveLine(Line ^line, 
  int newX1, int newY1, 
  int newX2, int newy2)
{
  line->x1 = newX1;
  line->y1 = newY1;
  line->x2 = newX2;
  line->y2 = newY1;
}

Syntaxe de l'appel:

Line ^line = gcnew Line(1, 2, 3, 4);

Line::MoveLine(line, 2, 4, 6, 8);
Console::WriteLine(line->x1);
Console::WriteLine(line->y1);
Console::WriteLine(line->x2);
Console::WriteLine(line->y2);
Le résultat est:
2
4
6
8

L'argument est passé par référence.
Si on modifie l'objet en utilisant la référence à l'intérieur de la fonction, on modifie l'instance originale.

Retour de fonction

Syntaxe Remarques
Par valeur Déclaration de la fonction:

Point CreatePoint(int a, int b)
{
  return Point(a, b);
}

Syntaxe de l'appel:

Point newPoint = 
  Point::CreatePoint(1, 2);

std::cout << newPoint.a << "\n";
std::cout << newPoint.b << "\n";
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:

Point &CreatePoint(int a, int b)
{
  Point newPoint(a, b);
  return newPoint;
}

Syntaxe de l'appel:

Point &newPoint = 
  Point::CreatePoint(1, 2);
Console::WriteLine(newPoint.a);
Console::WriteLine(newPoint.b);
Le résultat est:
1935459609
-1

ATTENTION: ne pas retourner une référence vers un objet créé dans la fonction.

Dans cet exemple, newPoint est alloué dans la pile. Quand on sort de la fonction, newPoint est supprimé de la pile et la référence retournée contient un objet supprimé.

Par référence
(A EVITER)
Déclaration de la fonction:

Point &CreatePoint(int a, int b)
{
  Point *newPoint = new Point(a, b);
  return *newPoint;
}

Syntaxe de l'appel:

Point &newPoint = 
  Point::CreatePoint(1, 2);
Console::WriteLine(newPoint.a);
Console::WriteLine(newPoint.b);
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.
Il faut éviter cette implémentation car elle peut mener à des fuites mémoires, l'objet n'est jamais supprimé du tas.

Par pointeur Déclaration de la fonction:

Point *Point::CreatePoint(int a, int b)
{
  return new Point(a, b);
}

Syntaxe de l'appel:

Point *newPoint = 
  Point::CreatePoint(1, 2);

Console::WriteLine(newPoint.a);
Console::WriteLine(newPoint.b);
delete newPoint;
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:

Line ^CreateLine(int x1, int y1, 
  int x2, int y2)
{
  return gcnew Line(x1, y1, x2, y2);
}

Syntaxe de l'appel:

Line ^line = 
  Line::CreateLine(1, 2, 3, 4);
Console::WriteLine(line->x1);
Console::WriteLine(line->y1);
Console::WriteLine(line->x2);
Console::WriteLine(line->y2);
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:

#include "Point.h"

class Circle
{
public:
    Circle(Point center, int radius);

    Point center;
    int radius;
};

Définition .cpp:

#include "Circle.h"

Circle::Circle(Point center, int radius)
{
    this->center = center;
    this->radius = radius;
}
  • La déclaration suivante nécessite un constructeur par défaut dans la classe Point:
    Point center;
  • Le passage d'argument dans le constructeur implique un constructeur de copie.
  • Il faut éviter ce type de membre si l'objet est complexe car des copies sont effectuées lors des initialisations et passage d'arguments.
Par référence Définition .h:

#include "Point.h"

class Circle
{
public:
    Circle(Point center, int radius);

    Point &center;
    int radius;
};

Définition .cpp:

#include "Circle.h"

Circle::Circle(Point &center, int radius) :
  center(center)
{
  this->center = center);
  this->radius = radius;
}
  • La référence doit être initialisée dans le constructeur (car la référence nulle n'existe pas).
  • La classe Circle ne peut pas implémenter de constructeur par défaut.
  • Il est conseillé d'utiliser un membre de type référence si la durée de vie du membre est aussi longue que celle de la classe.
Par référence (avec const) Définition .h:

#include "Point.h"

class Circle{
public:
     Circle();
     
     const Point &center;
     int radius;
};

Définition .cpp:

#include "Circle.h"

Circle::Circle() :center(Point(0, 0))
{}
Par pointeur Définition .h:

#include "Point.h"

class Circle
{
public:
    Circle(Point &center, int radius);

    Point *center;
    int radius;
};

Définition .cpp:

#include "Circle.h"

Circle::Circle(Point &center, int radius)
{
  this->center = &center;
  this->radius = radius;
}
  • Cette implémentation convient quand on doit instancier ou détruire le membre pendant la durée de vie de la classe.
  • Attention à la destruction du membre quand la classe est détruite.
Par référence managée
(classe managée)
Définition .h:

#include "Line.h"

public ref class Canvas
{
public:
    Canvas(Line ^line);

    Line ^line;
};

Définition .cpp:

#include "Canvas.h"

Canvas::Canvas(Line ^line)
{
  this->line = line;
}
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"
Point firstPoint(1, 2);
Point secondPoint(3, 4);
Point thirdPoint(3, 4);

if (firstPoint == secondPoint)
    cout &kt;&kt; "KO\n";
else if (secondPoint == thirdPoint)
    cout &kt;&kt; "OK\n";
Le résultat est:
OK

On peut aussi surcharger l'opérateur de cette façon:

bool operator==(Point pointA, Point pointB)
{
    return pointA.a == pointB.a 
        && pointA.b == pointB.b;
};
Comparaison de références
const Point &firstRef(1, 2);
const Point &secondRef(3, 4);
const Point &thirdRef(3, 4);

if (firstPoint == secondPoint)
    cout << "KO\n";
else if (secondPoint == thirdPoint)
    cout << "OK\n";
Le résultat est:
OK

Si on surcharge l'opérateur de cette façon (sans const):

bool operator==(Point pointA, Point pointB)
{
    return pointA.a == pointB.a 
        && pointA.b == pointB.b;
};

On obtiendra une erreur de compilation.

Comparaison de pointeurs
Point *firstPointer = new Point(1, 2);
Point *secondPointer = new Point(3, 4);
Point *thirdPointer = new Point(3, 4);

if (*firstPointer == *secondPointer)
    cout << "KO\n";
else if (*secondPointer == *thirdPointer)
    cout << "OK\n";
Le résultat est:
OK

Il n'est pas possible de déclarer la surcharge suivante:

bool operator==(Point *pointA, Point *pointB)
{
    return pointA->a == pointB->a 
        && pointA->b == pointB->b;
};

Ce type de surcharge pourrait préter à confusion dans le cas où on compare des pointeurs directement:

if (firstPointer == secondPointer)
{
    // ...
}
Références

Leave a Reply