« Programmation C-C++/C++ : La couche objet/Surcharge des opérateurs » : différence entre les versions

Contenu supprimé Contenu ajouté
m Révocation des modifications de 41.100.153.53 (discussion) vers la dernière version de 78.245.76.154
Balise : Révocation
DannyS712 (discussion | contributions)
m <source> -> <syntaxhighlight> (phab:T237267)
 
Ligne 46 :
=== Exemple 8-16. Surcharge des opérateurs internes ===
 
<sourcesyntaxhighlight lang=cpp>
class complexe
{
Ligne 129 :
return *this;
}
</syntaxhighlight>
</source>
 
{{note|La bibliothèque standard C++ fournit une classe traitant les nombres complexes de manière complète, la classe complex. Cette classe n'est donc donnée ici qu'à titre d'exemple et ne devra évidemment pas être utilisée. La définition des nombres complexes et de leur principales propriétés sera donnée dans la Section 14.3.1, où la classe complex sera décrite.}}
Ligne 155 :
=== Exemple 8-17. Surcharge d'opérateurs externes ===
 
<sourcesyntaxhighlight lang=cpp>
class complexe
{
Ligne 208 :
return result /= c2;
}
</syntaxhighlight>
</source>
 
Avec ces définitions, il est parfaitement possible d'effectuer la multiplication d'un objet de type complexe avec une valeur de type double. En effet, cette valeur sera automatiquement convertie en complexe grâce au constructeur de la classe complexe, qui sera utilisé ici comme constructeur de transtypage. Une fois cette conversion effectuée, l'opérateur adéquat est appliqué.
Ligne 216 :
{{notedebut}}Certains compilateurs peuvent supprimer la création des variables temporaires lorsque celles-ci sont utilisées en tant que valeur de retour des fonctions. Cela permet d'améliorer grandement l'efficacité des programmes, en supprimant toutes les copies d'objets inutiles. Cependant ces compilateurs sont relativement rares et peuvent exiger une syntaxe particulière pour effectuer cette optimisation. Généralement, les compilateurs C++ actuels suppriment la création de variable temporaire dans les retours de fonctions si la valeur de retour est construite dans l'instruction return elle-même. Par exemple, l'opérateur d'addition peut être optimisé ainsi :
 
<sourcesyntaxhighlight lang=cpp>
complexe operator+(const complexe &c1, const complexe &c2)
{
return complexe(c1.m_x + c2.m_x, c1.m_y + c2.m_y);
}
</syntaxhighlight>
</source>
 
Cette écriture n'est cependant pas toujours utilisable, et l'optimisation n'est pas garantie.
Ligne 230 :
Par exemple, si l'on veut optimiser la multiplication à gauche par un scalaire pour la classe complexe, on devra procéder comme suit :
 
<sourcesyntaxhighlight lang=cpp>
complexe operator*(double k, const complexe &c)
{
Ligne 236 :
return result;
}
</syntaxhighlight>
</source>
 
ce qui permettra d'écrire des expressions du type :
Ligne 261 :
Un autre problème important est celui de l'autoaffectation. Non seulement affecter un objet à lui-même est inutile et consommateur de ressources, mais en plus cela peut être dangereux. En effet, l'affectation risque de détruire les données membres de l'objet avant même qu'elles ne soient copiées, ce qui provoquerait en fin de compte simplement la destruction de l'objet ! Une solution simple consiste ici à ajouter un test sur l'objet source en début d'opérateur, comme dans l'exemple suivant :
 
<sourcesyntaxhighlight lang=cpp>
classe &classe::operator=(const classe &source)
{
Ligne 271 :
return *this;
}
</syntaxhighlight>
</source>
 
Enfin, la copie des données peut lancer une exception et laisser l'objet sur lequel l'affectation se fait dans un état indéterminé. La solution la plus simple dans ce cas est encore de construire une copie de l'objet source en local, puis d'échanger le contenu des données de l'objet avec cette copie. Ainsi, si la copie échoue pour une raison ou une autre, l'objet source n'est pas modifié et reste dans un état stable. Le pseudo-code permettant de réaliser ceci est le suivant :
 
<sourcesyntaxhighlight lang=cpp>
classe &classe::operator=(const classe &source)
{
Ligne 286 :
return *this;
}
</syntaxhighlight>
</source>
 
{{notedebut}}Le problème de l'état des objets n'est pas spécifique à l'opérateur d'affectation, mais à toutes les méthodes qui modifient l'objet, donc, en pratique, à toutes les méthodes non const. L'écriture de classes sûres au niveau de la gestion des erreurs est donc relativement difficile.
Ligne 325 :
=== Exemple 8-18. Opérateurs d'incrémentation et de décrémentation ===
 
<sourcesyntaxhighlight lang=cpp>
class Entier
{
Ligne 350 :
}
};
</syntaxhighlight>
</source>
 
{{note|Les opérateurs suffixés créant des objets temporaires, ils peuvent nuire gravement aux performances des programmes qui les utilisent de manière inconsidérée. Par conséquent, on ne les utilisera que lorsque cela est réellement nécessaire. En particulier, on évitera d'utiliser ces opérateurs dans toutes les opérations d'incrémentation des boucles d'itération.}}
Ligne 362 :
=== Exemple 8-19. Implémentation d'une classe matrice ===
 
<sourcesyntaxhighlight lang=cpp>
class matrice
{
Ligne 448 :
return lignes[i][j];
}
</syntaxhighlight>
</source>
 
Ainsi, on pourra effectuer la déclaration d'une matrice avec :
Ligne 470 :
=== Exemple 8-20. Opérateur de déréférencement et d'indirection ===
 
<sourcesyntaxhighlight lang=cpp>
// Cette classe est encapsulée par une autre classe :
struct Encapsulee
Ligne 508 :
return ;
}
</syntaxhighlight>
</source>
 
== Opérateurs d'allocation dynamique de mémoire ==
Ligne 522 :
=== Exemple 8-21. Détermination de la taille de l'en-tête des tableaux ===
 
<sourcesyntaxhighlight lang=cpp>
#include <stdio.h>
 
Ligne 550 :
return 0;
}
</syntaxhighlight>
</source>
 
Il est à noter qu'aucun des opérateurs new, delete, new[] et delete[] ne reçoit le pointeur this en paramètre : ce sont des opérateurs statiques. Cela est normal puisque, lorsqu'ils s'exécutent, soit l'objet n'est pas encore créé, soit il est déjà détruit. Le pointeur this n'existe donc pas encore (ou n'est plus valide) lors de l'appel de ces opérateurs.
Ligne 585 :
=== Exemple 8-22. Opérateurs new avec placement ===
 
<sourcesyntaxhighlight lang=cpp>
#include <stdlib.h>
 
Ligne 634 :
return 0;
}
</syntaxhighlight>
</source>
 
Dans cet exemple, la gestion de la mémoire est réalisée par les opérateurs new et delete normaux. Cependant, la réutilisation de la mémoire allouée se fait grâce à un opérateur new avec placement, défini pour l'occasion. Ce dernier ne fait strictement rien d'autre que de renvoyer le pointeur qu'on lui a passé en paramètre. On notera qu'il est nécessaire d'appeler explicitement le destructeur de la classe A avant de réutiliser la mémoire de l'objet, car aucune expression delete ne s'en charge avant la réutilisation de la mémoire.
Ligne 642 :
Il est impossible de passer des paramètres à l'opérateur delete dans une expression delete. Cela est dû au fait qu'en général on ne connaît pas le contexte de la destruction d'un objet (alors qu'à l'allocation, on connaît le contexte de création de l'objet). Normalement, il ne peut donc y avoir qu'un seul opérateur delete. Cependant, il existe un cas où l'on connaît le contexte de l'appel de l'opérateur delete : c'est le cas où le constructeur de la classe lance une exception (voir le Chapitre 9 pour plus de détails à ce sujet). Dans ce cas, la mémoire allouée par l'opérateur new doit être restituée et l'opérateur delete est automatiquement appelé, puisque l'objet n'a pas pu être construit. Afin d'obtenir un comportement symétrique, il est permis de donner des paramètres additionnels à l'opérateur delete. Lorsqu'une exception est lancée dans le constructeur de l'objet alloué, l'opérateur delete appelé est l'opérateur dont la liste des paramètres correspond à celle de l'opérateur new qui a été utilisé pour créer l'objet. Les paramètres passés à l'opérateur delete prennent alors exactement les mêmes valeurs que celles qui ont été données aux paramètres de l'opérateur new lors de l'allocation de la mémoire de l'objet. Ainsi, si l'opérateur new a été utilisé sans placement, l'opérateur delete sans placement sera appelé. En revanche, si l'opérateur new a été appelé avec des paramètres, l'opérateur delete qui a les mêmes paramètres sera appelé. Si aucun opérateur delete ne correspond, aucun opérateur delete n'est appelé (si l'opérateur new n'a pas alloué de mémoire, cela n'est pas grave, en revanche, si de la mémoire a été allouée, elle ne sera pas restituée). Il est donc important de définir un opérateur delete avec placement pour chaque opérateur new avec placement défini. L'exemple précédent doit donc être réécrit de la manière suivante :
 
<sourcesyntaxhighlight lang=cpp>
#include <stdlib.h>
 
Ligne 720 :
return 0;
}
</syntaxhighlight>
</source>
 
{{notedebut}}Il est possible d'utiliser le placement avec les opérateurs new[] et delete[] exactement de la même manière qu'avec les opérateurs new et delete.
Ligne 731 :
Enfin, vos opérateurs new et new[] doivent, en cas de manque de mémoire, appeler un gestionnaire d'erreur. Le gestionnaire d'erreur fourni par défaut lance une exception de classe std::bad_alloc (voir le Chapitre 9 pour plus de détails sur les exceptions). Cette classe est définie comme suit dans le fichier d'en-tête new :
 
<sourcesyntaxhighlight lang=cpp>
class bad_alloc : public exception
{
Ligne 741 :
virtual const char *what(void) const throw();
};
</syntaxhighlight>
</source>
 
{{note|Comme son nom l'indique, cette classe est définie dans l'espace de nommage std::. Si vous ne voulez pas utiliser les notions des espaces de nommage, vous devrez inclure le fichier d'en-tête new.h au lieu de new. Vous obtiendrez de plus amples renseignements sur les espaces de nommage dans le Chapitre 11.}}
Ligne 748 :
La classe exception dont bad_alloc hérite est déclarée comme suit dans le fichier d'en-tête exception :
 
<sourcesyntaxhighlight lang=cpp>
class exception
{
Ligne 758 :
virtual const char *what(void) const throw();
};
</syntaxhighlight>
</source>
 
{{note|Vous trouverez plus d'informations sur les exceptions dans le Chapitre 9.}}
Ligne 773 :
=== Exemple 8-23. Utilisation de new sans exception ===
 
<sourcesyntaxhighlight lang=cpp>
char *data = new(std::nothrow) char[25];
if (data == NULL)
Ligne 780 :
&vellip;
}
</syntaxhighlight>
</source>
 
{{note|La plupart des compilateurs ne respectent pas les règles dictées par la norme C++. En effet, ils préfèrent retourner la valeur nulle en cas de manque de mémoire au lieu de lancer une exception. On peut rendre ces implémentations compatibles avec la norme en installant un gestionnaire d'erreur qui lance lui-même l'exception std::bad_alloc.}}