Définir dans une classe une fonction membre qui a le même nom qu'une fonction standard est possible, mais risque de poser problème lors de
l'utilisation de cette fonction membre à l'intérieur de la classe. La fonction membre masque la fonction standard.
Il reste toutefois possible d'utiliser la fonction standard en faisant précéder son nom de ::.
class MaClasse
{
// ....int abs(int x); // masque la fonction standard abs // ....
}
int MaClasse::abs(int x)
{
return ::abs(x); // fait appel à la fonction standard abs()
}
oui, il est tout à fait possible d'utiliser des librairies écrites en C dans un programme C++ pour autant qu'elles soient déclarées
correctement dans les fichiers d'en-tête en utilisant le extern "C" qui indique que la fonction doit être considérée comme du code C
et non C++.
Contrairement aux classes 'normales', les templates ne peuvent pas avoir leur interface définie dans un fichier d'en-tête et leur
implémentation dans un fichier .cpp.
Il faut absolument que le template soit entièrement défini dans le fichier d'en-tête (.h).
Une autre façon de faire permet de garder la logique du .h contenant l'interface et le .cpp contenant l'implémentation est la suivante :
// le .h
#ifndef MON_TEMPLATE_H
#define MON_TEMPLATE_H
template <class T>
class Liste { ... };
#include "mon_template.cpp"
#endif
// le .cpp
#ifdef MON_TEMPLATE_H // évite les erreurs de compilation ;-)
template <class T>
Liste<T>::Liste () { ... }
#endif
Malheureusement, non, ce mécanisme n'est pas possible. Un catch ne pouvant capturer qu'un seul type d'exceptions, il faut définir autant de
blocs try/catch qu'il y a d'exceptions possibles.
L'utilisation de
try {
// ...
} catch(...) {
// ...
}
permet de capturer toutes les exceptions pouvant survenir, mais il est, dans ce cas, impossible de faire la distinction.
J'applique une méthode simple : la question à se poser est la suivante : est-ce que X est un genre de Y, ou est-ce que X utilise un Y ?
Si la réponse est X est un genre de Y, il s'agit d'un cas où je dérive une classe.
Si la réponse est X utilise Y, il s'agit d'un cas où je vais encapsuler une classe.
La raison la plus probable est l'absence de la directive using namespace std;. Cette directive était optionnelle avec gcc 2.x
(ceci était un défaut de conformité au standard). Il faut maintenant, avec gcc 3.x qui est conforme au standard, la mettre dans chaque
fichier source, après le bloc des #include. Le programme ainsi modifié compilera aussi bien avec gcc 2.x que gcc 3.x.
Le fichier iostream.h est présent par compatibilité avec les anciennes versions de C++ quand les namespaces n'existaient pas ;
il ne faut plus l'utiliser dans les nouvelles applications.
De la même manière, pour la bibliothèque standard C, il est recommandé pour des raisons d'uniformisation d'inclure les noms de
fichiers sans .h : ils sont préfixés par la lettre "c" (pour souligner le fait que ça vient du C). utilisez
Une auto-affectation a lieu quand quelqu'un affecte un objet à lui-même.
#include "Fred.hpp" // Déclaration de la classe Fredvoid userCode(Fred& x)
{
x = x; // Auto-affectation
}
Bien évidemment, personne n'écrit du code pareil, mais parce que des pointeurs ou des références distinctes peuvent désigner le même
objet (c'est l'aliasing), des auto-affectations peuvent avoir lieu derrière votre dos.
void userCode(Fred& x, Fred& y)
{
x = y; // C'est une auto-affectation si &x == &y
}
int main()
{
Fred z;
userCode(z, z);
return 0;
}
Si vous ne prenez pas en compte le cas de l'auto-affectation, vous exposez les utilisateurs de vos classes à des bugs subtils qui peuvent
avoir des conséquences désastreuses. Par exemple, l'affectation d'un objet de la classe ci-dessous à lui-même va poser un très gros
problème.
class Wilma
{
};
class Fred {
public:
Fred() : p_(new Wilma()) { }
Fred(const Fred& f) : p_(new Wilma(*f.p_)) { }
~Fred() { delete p_; }
Fred& operator= (const Fred& f)
{
// Ce code n'est pas bon: il ne traite pas le cas de l'auto-affectation!delete p_; // Ligne 1
p_ = new Wilma(*f.p_); // Ligne 2return *this;
}
private:
Wilma* p_;
};
Si quelqu'un assigne un objet de type Fred à lui-même, la ligne 1 va détruire à la fois this->p_ et f.p_ puisque *this et f désignent
ici le même objet. Juste derrière, la ligne 2 utilise *f.p_, mais cet objet n'est plus valide puisqu'il a été détruit. Inutile de vous
dire que cette utilisation risque fort de s'avérer catastrophique.
Retenez qu'il est votre responsabilité, en tant qu'auteur de la classe Fred, de garantir que l'affectation d'un objet de type Fred à
lui-même ne pose pas de problèmes. Ne partez pas du principe que les utilisateurs de vos classes ne feront jamais ce genre
d'affectation. Et ce sera de votre faute si un objet de votre classe fait crasher le programme dans le cas où on l'affecte à lui-même.
Notez aussi que dans l'exemple ci-dessus, l'opérateur Fred::operator= (const Fred&) contient un autre bug: Si une exception est
lancée lors de l'évaluation de new Wilma(*f.p_) (par exemple., une exception plus-de-mémoire ou une exception lancée par le constructeur
par copie de Wilma), this->p_ va se retrouver pointant sur de la mémoire qui n'est plus valide. La solution consiste à allouer les
nouveaux objets avant de détruire les anciens.
Vous devez vous poser la question de l'auto-affectation à chaque fois que vous créez une classe. Mais ça ne veut pas dire qu'il est
nécessaire que vous ajoutiez du code à toutes vos classes: ajouter du code n'est pas utile si vos objets peuvent être assignés à
eux-mêmes sans que cela pose un problème.
Si vous devez modifier votre opérateur d'affectation, voici une technique simple et efficace :
Fred& Fred::operator= (const Fred& f)
{
if (this == &f) return *this; // Traite correctement le cas de l'auto-affectation // Ici se trouve le code normal de l'opérateur d'affectation...return *this;
}
Ce test explicite n'est pas toujours nécessaire. Par exemple, si vous vouliez corriger l'opérateur d'affectation de la questionprécédente
de façon à ce que les exceptions lancées par new et/ou les exceptions lancées par le par le constructeur de copie de la classe Wilma
soient gérées correctement, vous écririez le code suivant. Et notez que ce code a pour (agréable) effet de bord de traiter correctement
le cas de l'auto-affectation :
Fred& Fred::operator= (const Fred& f)
{
// Ce code traite correctement (même si c'est implicite) le cas de l'auto-affectation
Wilma* tmp = new Wilma(*f.p_); // Pas de problème si une exception était levée icidelete p_;
p_ = tmp;
return *this;
}
Dans un cas comme ci-dessus (où l'auto-affectation est sans danger mais inefficace), certains programmeurs veulent quand même ajouter
"if (this == &f) return *this;" pour obtenir de meilleures performances dans le cas d'une auto-affectation. C'est la plupart du
temps un mauvais choix, car si une auto-affectation a lieu une fois sur mille, alors le if consommera inutilement des cycles processeur
dans 99,9% des cas.
Ce document issu de http://www.developpez.com est soumis à la licence
GNU FDL traduit en français
ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
Certaines parties de ce document sont sous copyright Marshall Cline