Les classes
En Java, les classes permettent de regrouper des attributs et des méthodes dans un nouveau
type de données. En C/C++, si on n’a que des attributs et pas de méthode, on peut utiliser
le type struct. Ce type permet de regrouper des attributs et est aussi disponible en C :
struct point {
int x;
int y;
}
On peut ensuite déclarer une variable de ce type :
point myPoint;
point myPoint = {12, 42};
ou en spécifiant les attributs :
point myPoint = {.x = 12, .y = 42};
On peut aussi combiner la déclaration du type et de la variable :
struct point {
int x;
int y;
} myPoint = {.x = 12, .y = 42};
Si on souhaite ajouter des méthodes à un type, nous pouvons le faire en C avec des pointeurs de fonctions, mais c’est une technique assez lourde et sujette aux erreurs. On préfère passer à C++ qui propose un vrai concept de classes et d’objets.
Nous pouvons déclarer une classe (par exemple, dans un fichier point.hpp) de la manière suivante :
class Point {
public:
Point();
Point(int x, int y);
void Move(int dx, int dy);
private:
int x_;
int y_;
};
La convention d’ajouter un underscore à la fin d’un attribut vient du “Google C++ Style Guide”.
Nous implémentons ensuite les méthodes dans un fichier point.cpp :
#include "point.hpp"
Point::Point() {
x_ = 0;
y_ = 0;
}
Point::Point(int x, int y) {
x_ = x;
y_ = y;
}
void Point::Move(int dx, int dy) {
x_ += dx;
y_ += dy;
}
Notez que les constructeurs (Point::Point) peuvent initialiser les
attributs avec une syntaxe simplifiée :
Point::Point() : x_(0), y_(0) {};
Point::Point(int x, int y) : x_(x), y_(y) {};
Tout comme en Java, il est possible de définir des membres de
classe statiques. Il suffit d’utiliser le mot clé static1.
Si un constructeur alloue des ressources (des fichiers, de la mémoire, …),
il faut aussi retourner ces ressources au système. Cette opération se fait
en général avec un destructeur. C’est comme en Java, sauf qu’en Java le
destructeur s’appelle finalize et en C++, est le nom de la classe
précédé du symbole ~. C’est une bonne pratique de toujours ajouter
un destructeur virtuel, même s’il est vide2 :
class Point {
public:
Point();
virtual ~Point() {}; // destructor
Point(int x, int y);
void Move(int dx, int dy);
private:
int x_;
int y_;
};
Prenons un autre exemple avec la définition d’une classe Duration qui implémente une durée en secondes.
class Duration {
public:
Duration();
explicit Duration(int seconds);
int GetSeconds() const;
void SetSeconds(int seconds);
private:
int seconds_;
};
Notez l’utilisation du mot clé explicit pour le constructeur qui prend un entier. Cela empêche les conversions implicites d’un entier en Duration, ce qui peut éviter des erreurs de programmation.
Une convertion implicite est illustrée dans le code suivant. Supposons que nous avons une fonction Wait qui prend un objet Duration en paramètre :
void Wait(Duration d) {
// ...
}
Si le constructeur de Duration n’était pas marqué comme explicit, nous pourrions appeler Wait(5), et le compilateur convertirait automatiquement l’entier 5 en un objet Duration.
Avec le mot clé explicit, cette conversion implicite est interdite, et nous devons créer un objet Duration explicitement :
Wait(Duration(5));
Bonne pratique
Il est recommandé d’utiliser le mot clé explicit pour les constructeurs à un seul argument afin d’éviter des conversions implicites non désirées.