This is an old revision of the document!
Une classe définit la structure d'un objet.
Elle définit d'une part un ensemble de champs et d'autre part un ensemble de fonctions membres qui accèdent aux champs définis au sein de la classe.
Cependant, la classe n'alloue pas de mémoire pour les champs. C'est au moment de la création de l'objet que la mémoire nécessaire à stocker l'ensemble des champs nécessaire est allouée. Une fois la mémoire allouée, il est parfois nécessaire d'initialiser l'ensemble des champs de l'objet afin de pouvoir utiliser l'objet dès que l'opération d'initialisation soit terminé.
De fait, la création d'un objet comporte deux phases :
Classiquement, la mémoire allouée pour un objet se trouve :
Cependant, il est possible d'avoir des mécanismes d'allocation mémoire dans d'autres zones que les zones précédemment énumérées en implantant des mécanismes d'allocation sur mesure.
Dans ce cas, la mémoire est allouée dans la pile. L'allocation mémoire est relativement rapide puisqu'il s'agit de réserver un bloc mémoire capable de contenir l'ensemble des champs ainsi que la table des méthodes virtuelles de l'objet. La mémoire n'est pas initialisée et les différents champs contiennent des valeurs indéterminées.
void display_message() { std::string message = "Un message"; message += " qui est complété."; std::cout << message << std::endl; }
Naturellement, le bloc mémoire disparait automatiquement lorsque l'on quitte la fonction.
std::string* get_message() { std::string message = "Un message"; return &message; } int main() { std::string* message = get_message(); std::cout << *message << std::endl; // Génère une segmentation fault puisque la mémoire // l'objet message a été supprimée à la fin // de la fonction. return 0: }
La mémoire est allouée dans le tas. Pour ce faire, un appel au gestionnaire de mémoire du système d'exploitation est effectuée pour retourner un bloc mémoire suffisamment grand pour stocker l'ensemble des champs ainsi que la table des méthodes virtuelles associés à l'objet.
void display_message() { std::string* message = new std::string("Un message"); // allocation de l'objet sur le tas. // c'est un pointeur sur un objet de type std::string // qui est retourné. message->append(" qui est complété."); std::cout << message << std::endl; delete message; // attention: la mémoire n'est pas automatiquement // libérée, il faut faire attention à ne pas oublier // de libérer la mémoire une fois que l'objet n'est // plus utilisé. }
De fait, la mémoire allouée n'est libérée que par un appel à
l'opérateur delete
. Ceci signifie qu'il est parfaitement
possible de retourner un pointeur sur un objet qui aurait été
alloué par une fonction.
std::string* get_message() { std::string* message = new std::string("Un message"); // allocation de l'objet sur le tas. // c'est un pointeur sur un objet de type std::string // qui est retourné. message->append(" qui est complété."); return message; } int main() { std::string* message = get_message(); std::cout << *message << std::endl; delete message; // attention: la mémoire n'est pas automatiquement // libérée, il faut faire attention à ne pas oublier // de libérer la mémoire une fois que l'objet n'est // plus utilisé. return 0: }
Il est parfaitement possible de déclarer un objet comme une variable globale. Dans ce cas, la mémoire de l'objet est allouée au moment du chargement du segment mémoire.
std::string message = "Un message"; void extend_message() { message->append(" qui est complété."); } int main() { extend_message(); std::cout << message << std::endl; return 0: }
Une fois allouée, l'objet doit être initialisé. Cette initialisation se produit après l'allocation mémoire de l'objet. L'initialisation est effectuée en appelant une méthode spéciale de l'objet qui est appelé le constructeur . Un constructeur est un méthode qui a le même nom que la classe, qui n'a pas de type de retour et qui prend aucune ou plusieurs paramètres.
Le code suivant défini deux constructeurs Point()
et Point(int, int)
pour la classe Point
.
class Point { private: double X, Y; public: Point() { X = 0.0; Y = 0.0; } Point(int x, int y) { X = x; Y = y; } };
Lorsque nous créons un objet sur la pile (ou en mémoire globale) :
void allocate_point_on_stack() { Point pointA; // appelle le constructeur par défaut, // ie. le constructeur Point() Point pointB(3.4, 3.5); // appelle le constructeur spécialisé prenant // deux valeurs en entrées, ie. le constructeur Point(double, double) }
La syntax est légèrement différente quand l'objet est alloué dans le tas :
void allocate_point_on_stack() { Point* pointA = new Point(); // appelle le constructeur par défaut, // ie. le constructeur Point() Point* pointB = new Point(3.4, 3.5); // appelle le constructeur spécialisé prenant // deux valeurs en entrées, ie. le constructeur Point(double, double) }
Cependant il est parfaitement possible d'utiliser une syntaxe équivalente pour allouer un objet sur la pile en écrivant l'allocation comme suit. Cependant cette écriture n'est pas recommandée, puisqu'elle peut parfois entrainer certaines confusions.
void allocate_point_on_stack() { Point pointA = Point(); // appelle le constructeur par défaut, // ie. le constructeur Point() Point pointB = Point(3.4, 3.5); // appelle le constructeur spécialisé prenant // deux valeurs en entrées, ie. le constructeur Point(double, double) }