Construction et destruction des objets Durée de vie des objets Objets composites Tableaux d'objets Copie (Initialisation et affectation) des objets Initialisation des objets Constructeur par recopieinit Opérateur d'affectation Sémantique de copie en C++ Retenir H. Afli, M1 C++, Université du Maine, 2012-2013
1/ initialisation des données membres avec des valeurs passées en argument au constructeur dans le corps du constructeur ou mieux par la liste d'initialisation 2/ allocation dynamique de mémoire pour des données membres dynamiques (s il y en a) 3/ construction automatique des objets membres (cas où les données membres sont d'un type utilisateur class )
principes de base : constructeur passage obligé pour la définition d'un objet destructeur passage obligé pour la destruction d'un objet toujours en deux phases dans l'ordre suivant constructeur : 1/ réservation de la mémoire allouée à l'objet et initialisation des membres 2/ exécution du corps du constructeur destructeur : 1/ exécution du corps du destructeur 2/ destruction des membres et libération de la mémoire allouée à l'objet
objets automatiques : déclarés dans une fonction ou un bloc alloués dans la pile lors de l'exécution de leur première déclaration (= définition) détruits à la fin de l'exécution du bloc objets statiques déclarés en dehors de toute fonction ou déclaration précédée du mot static créés avant le début de l'exécution de main détruits après la fin de l'exécution de main pour les objets statiques et automatiques le compilateur garantit qu'il appelle autant de destructeurs qu'il a appelé de constructeurs
objets dynamiques dans le tas par un appel à new détruits par un appel explicite à delete alloués objets temporaires créés par le compilateur par exemple : par appel explicite au constructeur d'une classe pour mettre le résultat d'une fonction avant de le retourner par valeur peuvent être détruits à tout moment après l'évaluation de l'expression qui les a créés
new : alloue dans le tas l'espace mémoire nécessaire à l'objet appelle un des constructeurs de l'objet retourne un pointeur sur l'espace alloué ou 0 si la mémoire demandée n'a pu être attribuée delete appelle le destructeur de l'objet libère la mémoire dynamiquement allouée à l'objet
remarques : conseil (ordre!) vérifiez la valeur de retour de new pour s'assurer que l'allocation s'est bien passée 4 méthodes : préhistorique : assert ( => abort) artisanale : if (! adr) abort ou exit... moderne : set_new_handler (ptr sur fct de gestion des erreurs définie par le programmeur) post-moderne : utiliser les exceptions
Construction et destruction des objets Durée de vie des objets Objets composites Tableaux d'objets Copie (Initialisation et affectation) des objets Initialisation des objets Constructeur par recopie Opérateur d'affectation Sémantique de copie Retenir
Principe : quand un objet est composé d'autres objets les objets composants sont créés en premier (dans l'ordre de leur déclaration) puis l'objet composite est créé 1/ appel aux constructeurs des objets membres pour allouer la mémoire des membres l'initialiser avec les paramètres fournis par la liste d'initialisation 2/ exécution du corps du constructeur de l'objet composé la destruction se fait dans l'ordre inverse exécution du corps du destructeur de l'objet composé exécution des destructeurs des membres libération de la mémoire allouée à l'objet
quand un objet A (objet composite) possède un membre (objet composant) dont le type est la classe B le constructeur de B sera appelé avant que le corps du constructeur de A ne soit exécuté le constructeur de A doit appeler un constructeur de la classe membre B connaître les arguments à passer à ce constructeur de B la déclaration du constructeur de A doit comporter des arguments à passer aux constructeurs des membres (objets composants) le passage d'arguments entre constructeurs se fait par la liste d'initialisation
2 solutions : affectation dans le corps du constructeur utilisation d'une liste d'initialisation par le constructeur liste d'initialisation : la définition du constructeur de l'objet composé spécifie dans l'en-tête par la liste d'initialisation des objets membres, les constructeurs des objets membres à appeler ainsi que les paramètres à leur communiquer ordre d'exécution du corps des constructeurs : constructeur de chaque objet membre invoqué dans l'ordre des déclarations de la classe (pas dans l'ordre de la liste d'initialisation) constructeur de la classe
Règle : pour initialiser les membres d'un objet préférez la liste d'initialisation à l'affectation dans le corps des constructeurs Utilisation de la liste d'initialisation : obligatoire dans le cas de membre référence ou de membres constants plus efficace dans le cas des membres objets question de style dans le cas de membres de type simple Conseil (Ordre!) : Ranger les membres dans une liste d'initialisation dans le même ordre que dans la déclaration de la classe
Construction et destruction des objets Durée de vie des objets Objets composites Tableaux d'objets Copie (Initialisation et affectation) des objets Initialisation des objets Constructeur par recopie Opérateur d'affectation Sémantique de copie Retenir
aucun moyen de préciser dans la définition d'un tableau des arguments pour le constructeur des objets du tableau la déclaration d'un tableau d'objets d'une classe n'est possible que si la classe possède un constructeur par défaut (sans argument) allocation dans la pile : truc tab[20] ; crée 20 objets de type truc (appelle pour chacun de ces objets le constructeur par défaut) et les place en mémoire consécutivement ; l'adresse du premier est dans tab allocation dans le tas truc * tab = new truc[20] ; destruction par : delete [ ] truc
Construction et destruction des objets Durée de vie des objets Objets composites Tableaux d'objets Copie (Initialisation et affectation) des objets Initialisation par copie des objets Constructeur par recopie Opérateur d'affectation Sémantique de copie Retenir
en C++ initialisation par un constructeur lorsqu'un objet est créé affectation par l'opérateur = pour changer la valeur d'une variable de type donné les initialisations sont réalisées par les constructeurs initialisation des membres effectuées : par la liste d'initialisation des constructeurs initialisation par un objet du même type : effectuée par un constructeur spécial le constructeur par recopie l'affectation est réalisée par l'opérateur d'affectation = par défaut les données membres du récepteur reçoivent une copie des données membres de l'original (copie superficielle)
Définition : fonction membre d'une classe portant le nom de la classe et sans valeur de retour possède un argument unique de type référence à un objet constant de la classe Truc (const Truc &) ; rôle : appel généré automatiquement par le compilateur quand un objet est créé et initialisé par un objet déjà construit et du même type que lui
par défaut : copie superficielle de toutes les valeurs des membres de l'initialiseur dans l'objet à construire (constructeur de recopie par défaut) utilisation d'un "constructeur de recopie" défini explicitement par le programmeur de la classe lorsque la copie superficielle est insuffisante
invoqué dans 3 cas : cas n 1: déclaration d'un objet avec un "initialiseur" déjà construit et du même type cas n 2: objet passé par valeur argument d'une fonction cas n 3: objet passé par valeur en retour d'une fonction Retenir 1 : Un appel de fonction où un argument est passé par valeur provoque un appel implicite au constructeur par copie Retenir 2 : Une fonction qui retourne un résultat par valeur provoque un appel implicite au constructeur par copie
2 façons d'initialiser un objet déclaration d'un objet avec initialiseur initialiseur est une expression d'un type quelconque à condition qu'il existe un constructeur à un argument de ce type création de l'objet par appel au constructeur concerné (usuel ou par recopie)
chaque classe possède un opérateur d'affectation par défaut qui effectue une copie membre à membre le programmeur peut surcharger cet opérateur si la sémantique de copie par défaut (copie superficielle des membres) ne lui convient pas protoype de l'opérateur d'affectation d'une classe Truc Truc & Truc::operator = (const Truc &) ;
Construction et destruction des objets Copie (Initialisation et affectation) des objets Initialisation des objets Constructeur par recopie Opérateur d'affectation Sémantique de copie Copie superficielle et copie profonde en C++ Constructeur par copie profonde Surcharge de l'affectation Retenir
copie superficielle d'un objet dans un autre copie membre à membre suffisante pour les objets dont les membres sont de types simples pour les membres alloués en mémoire dynamique copie de la valeur du pointeur sur la mémoire dynamique et non copie de l'objet pointé conduit à des problèmes de "partage de mémoire" ("alias de pointeurs") copie profonde d'un objet dans un autre : copie membre à membre pour les membres de types simples copie de l'objet pointé par les membres dynamiques duplication des membres dynamiques
affectation b = a par copie superficielle a a 5 _dyn 5 _dyn c o u c o u \0 c o u c o u \0 b b 3 _dyn 5 _dyn l é o \0 avant l é o \0 après
affectation b = a par copie profonde a 5 _dyn adr1 a 5 _dyn c o u c o u \0 adr1 adr1 b 3 _dyn adr2 b adr2 5 _dyn adr3 adr2 l é o \0 l é o \0 adr3 avant c o u c o u \0 c o u c o u \0 après
situation : objets ayant des membres dynamiques copie : initialisation par un objet de même type déjà construit passage par valeur d'un paramètre ou du résultat d'une fonction ou affectation trois problèmes fuites de mémoire pointeur fou partage d'rmations (ce peut être volontaire)
avant affectation b = a par copie superficielle après a a 5 _dyn 5 _dyn c o u c o u \0 c o u c o u \0 b b 3 _dyn 5 _dyn l é o \0 l é o leo n'est pas désallouée n'est plus accessible \0
après destruction de b après une copie superficielle a a 5 _dyn 5 _dyn c o u c o u \0 a.inf_dyn pointe sur une zone qui a été désallouée b _dyn 5
a 5 _dyn c o u c o u \0 b 5 _dyn modifier a->_dyn modifie b->_dyn et réciproquement ce peut être voulu il faut en avoir conscience
une seule solution : quand des objets possèdent des données membres dynamiques il faut dans la classe définir un destructeur et définir une sémantique de copie i.e. définir un constructeur par recopie ET surcharger l'opérateur d'affectation (l'un ne va pas sans l'autre dans un une bonne classe) 2 stratégies dupliquer les données dynamiques : copie profonde gérer le partage de données : compteur de références
Partage des données dynamiques (compteur de références) duplication des données dynamiques a a 5 _dyn _dyn 2 c o u c o u \0 cpt de ref données c o u c o u \0 b 5 b 5 _dyn 5 _dyn c o u c o u \0 cpt de ref données 0 l é o \0
doit contenir les instructions permettant : d'allouer de la mémoire dynamique pour les données à dupliquer d'initialiser correctement les membres données du nouvel objet de recopier les valeurs à dupliquer
Construction et destruction des objets Copie (Initialisation et affectation) des objets Initialisation des objets Constructeur par recopie Opérateur d'affectation Sémantique de copie Copie superficielle et copie profonde en C++ Constructeur par copie profonde Surcharge de l'affectation Retenir
fonction membre de la classe sa signature pour une classe Chose est Chose & Chose::operator= (const Chose &) retourne une référence à *this pour permettre les affectations en cascade unique argument une référence constante à l'objet à copier dans une stratégie de duplication, le corps : b = a s'écrit en 4 étapes 1/ ne rien faire si les 2 objets sont identiques 2/ libération de la mémoire des parties dynamiques de b 3/ allocation dynamique d'un nouvel emplacement pour les valeurs des données membres dynamiques à copier et recopie de ces valeurs 4/ recopie des autres données membres
Construction et destruction des objets Copie (Initialisation et affectation) des objets Retenir Passage par valeur d'arguments Destruction des objets Objets possédant des membres dynamiques
rappelez vous que le passage d'un objet par valeur en argument d'une fonction en valeur de retour d'une fonction se fait par appel au constructeur de recopie s'il est défini par défaut (copie superficielle ) Conséquence : on passe les objets en paramètre : par référence (quand on veut les modifier) et mieux par référence constante (quand on veut économiser une copie ) en retour de fonction par valeur quand ce sont des objets créés à l'intérieur de la fonction (copie)
Tout objet C++ possède un constructeur et un destructeur un destructeur explicite n'est utile QUE SI la classe a des membres dynamiques dans ce cas le corps du destructeur doit comprendre autant d'appel à delete qu'il y a eu d'appel à new pour la construction de l'objet (en général) doit détruire toutes les données dynamiques dont l'objet est propriétaire et qu'il a "adoptées"
définir des constructeurs qui réservent de la mémoire sur le tas pour les membres dynamiques définir un destructeur pour libérer la mémoire allouée par les constructeurs ou adoptée par l'objet pour que les objets puissent être passés par valeur en paramètre ou en retour de fonction définir une sémantique de copie, i.e. un constructeur par recopie un opérateur d'affectation