IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo
Sommaire > Les classes en C++
        Qu'est-ce qu'une classe ?
        Quand est-ce qu'une classe a une sémantique de valeur ?
        Quand est-ce qu'une classe a une sémantique de d'entité ?
        Comment structurer ma classe en un fichier .h et un fichier .cpp ?
        Qu'est-ce 'this' ?
        Comment puis-je empêcher les autres programmeurs de violer l'encapsulation en accédant aux membres privés de mes classes ?
        Comment dériver une classe à partir d'une autre ?
        Comment faire pour empêcher de créer plus d'une instance d'une classe ?
        Que signifient public, private et protected ?
        Quelle est la différence entre class et struct ?
        Comment créer 2 classes qui font référence l'une à l'autre ?
        Que signifie objet.fonction1().fonction2() ?
        Comment effectuer la copie d'objets polymorphes ?
        Qu'est-ce qu'une classe abstraite ?
        Quelle forme canonique adopter en fonction de la sémantique de la classe ?
        Qu'est-ce que la forme canonique orthodoxe de Coplien ?
        Quand dois-je définir un constructeur par défaut ?
        Quand dois-je définir un constructeur par copie ?
        Quand dois-je définir l'opérateur d'affectation ?
        Quand dois-je définir un destructeur ?
        Que conclure de la forme canonique orthodoxe de Coplien ?
5.1. Les constructeurs (15)
                Qu'est-ce qu'un constructeur ?
                Qu'est-ce qu'un constructeur par défaut ?
                Qu'est-ce qu'un constructeur de copie ?
                Quelles sont les différences fondamentales entre le constructeur d'une classe et sa méthode Init() ?
                Y a-t-il une différence quelconque entre MaClasse x; et MaClasse x(); ?
                Un constructeur d'une classe peut-il appeler un autre constructeur de la même classe pour initialiser 'this' ?
                Est-ce que le constructeur par défaut pour Fred est toujours Fred::Fred() ?
                Quel constructeur est appelé quand je crée un tableau d'objets Fred ?
                Mes constructeurs doivent-ils utiliser les listes d'initialisation ou l'affectation ?
                Puis-je utiliser le pointeur this dans un constructeur ?
                Puis-je appeler des fonctions virtuelles dans le constructeur (ou le destructeur) ?
                Qu'est-ce que l'idiome du constructeur nommé (Named Constructor) ?
                Que faire en cas d'échec du constructeur ?
                Qu'est-ce que 'l'idiome des paramètres nommés' ?
                Dans quel ordre sont construits les différents composants d'une classe ?
5.2. Les destructeurs (11)
                Qu'est-ce qu'un destructeur ?
                Est-il possible d'invoquer explicitement le destructeur d'une classe ?
                Dans quel ordre les objets locaux sont-ils détruits ?
                Dans quel ordre les objets contenus dans un tableau sont-ils détruits ?
                Doit-on détruire explicitement les objets locaux ?
                Et si on veut absolument qu'un objet local 'meure' avant l'accolade fermant le bloc dans lequel il a été créé ?
                Et s'il n'est pas possible de placer l'objet local dans un bloc artificiel ?
                Peut-on détruire explicitement un objet alloué par new ?
                Dans le code d'un destructeur, doit-on détruire explicitement les objets membres ?
                Dans le code du destructeur d'une classe dérivée, doit-on appeler explicitement le destructeur de la classe de base ?
                Pourquoi et quand faut-il créer un destructeur virtuel ?
5.3. Les amis (friend) (5)
                Que signifie 'friend' ?
                Les amis brisent-ils l'encapsulation ?
                Quels avantages/désavantages y a-t-il à utiliser des fonctions friend ?
                Que signifie 'l'amitié n'est ni héritée ni transitive, ni réciproque' ?
                Doit-on utiliser plutôt des fonctions membres ou plutôt des fonctions friend ?
5.4. Les données et fonctions membres statiques (9)
                Que signifie la déclaration suivante : 'static const int MAX = 10' ?
                Pourquoi déclarer un membre static dans une classe ?
                Comment initialiser un membre static ?
                Pourquoi les classes avec des membres statiques me donnent-elles des erreurs lors de l'édition des liens ?
                Qu'est-ce que le 'fiasco dans l'ordre d'initialisation des variables statiques' ?
                Comment puis-je éviter le 'fiasco dans l'ordre d'initialisation des variables statiques' ?
                Pourquoi l'idiome de construction à la première utilisation utilise-t-il un pointeur statique plutôt qu'un objet statique ?
                Comment puis-je éviter le 'fiasco dans l'ordre d'initialisation des variables statiques' pour les données membres statiques ?
                Dois-je me préoccuper du 'fiasco dans l'ordre d'initialisation des variables statiques' pour les types de base ?
5.5. Sémantique de copie (8)
                Qu'est-ce qu'une classe copiable ?
                Je n'ai pas de constructeur par copie ni d'opérateur d'affectation (operator=) : ma classe est-elle copiable ?
                Toute classe doit-elle être copiable ?
                Comment rendre une classe non copiable ?
                Que se passe-t-il pour les classes dérivées d'une classe non copiable ?
                Quelle solution préférer (héritage ou redéfinition) pour rendre une classe non copiable ?
                Comment rendre une classe non copiable en C++0x ?
                Quel est l'équivalent de boost::noncopyable en C++0x ?



Qu'est-ce qu'une classe ?
Mise à jour le 19/10/2004[haut]
auteurs : LFE, Aurélien Regat-Barrel
D'une façon très simple et en faisant le parallèle avec le C, on peut dire qu'une classe est une structure à laquelle on ajoute des fonctions permettant de gérer cette structure.
Mais outre l'ajout de ces fonctions, il existe deux grandes nouveautés par rapport aux structures du C:

  • d'une part, les membres de classe, qu'ils soient des variables ou des fonctions, peuvent être privés c'est-à-dire inaccessibles en dehors de la classe (par opposition aux membres publics d'une structure C où tous ses membres sont accessibles).
  • d'autre part, une classe peut être dérivée. La classe dérivée hérite alors de toutes les propriétés et fonctions de la classe mère. Une classe peut d'ailleurs hériter de plusieurs classes simultanément.

Une classe se déclare via le mot-clé class suivi du nom de la classe, d'un bloc (accolades ouvrante et fermante) et d'un point virgule (ne pas l'oublier !). Les membres de la classe (variables ou fonctions) doivent être déclarés à l'intérieur de ce bloc, à la manière des structures en C.
class MaClasse 
{ 
    // Déclaration de toutes les variables ou fonctions membres
    // constitutives de la classe

    int a; // variable membre
    void b(); // fonction membre
};

Quand est-ce qu'une classe a une sémantique de valeur ?
Créé le 15/10/2009[haut]
auteurs : Medinoc, 3DArchi
On dit d'une classe qu'elle a une sémantique de valeur si deux objets situés à des adresses différentes, mais au contenu identique, sont considérés égaux.
Par exemple, une classe modélisant une somme d'argent a une sémantique de valeur. Une somme de 100 € est toujours une somme de 100€. On peut ajouter, soustraire, multiplier différentes sommes d'argent. Deux sommes de 100 € sont identiques même si l'une est sous forme d'espèces et l'autre sous forme de chèque.
On voit qu'il peut être pertinent :

  • de redéfinir des opérateurs arithmétiques (+, -, *, /) ;
  • d'avoir un opérateur d'affectation (=, constructeur par copie ?) ;
  • de redéfinir des opérateurs de comparaison (==, <, etc.).
Inversement, une classe à sémantique de valeur n'a pas beaucoup de sens pour servir de classe de base à un héritage. On ne trouvera donc en général pas de fonction virtuelle dans une classe à sémantique de valeur.


Quand est-ce qu'une classe a une sémantique de d'entité ?
Créé le 15/10/2009[haut]
auteurs : Davidbrcz, 3DArchi
A l'inverse des classes à sémantique de valeur, une classe a une sémantique d'entité si toutes les instances de cette classe sont nécessairement deux à deux distinctes, même si tous les champs de ces instances sont égaux. Elle modélise un concept d'identité : chaque objet représente un individu unique.
Une classe modélisant un compte a une sémantique d'entité. Deux comptes sont distincts même s'ils ont la même somme d'argent. Cela n'a pas de sens d'ajouter des comptes (on peut vider un compte pour le verser dans un autre, mais ce n'est pas un ajout). En revanche, on peut avoir des comptes courants, des comptes d'épargnes, des comptes titres, etc.
On voit qu'une classe à sémantique d'entité peut servir de base à un héritage. Mais, une classe à sémantique d'entité :

  • ne redéfinit pas les opérateurs arithmétiques (+,-,/*) ;
  • n'a pas d'opérateur d'affectation (=, constructeur par copie) ;
  • ne redéfinit pas les opérateurs de comparaison (==, < etc.).
Si on veut créer une copie d'un objet à sémantique d'entité, on s'appuie sur une méthode Clone spécifique retournant un nouvel objet dans un état semblable.


Comment structurer ma classe en un fichier .h et un fichier .cpp ?
Mise à jour le 03/02/2007[haut]
auteurs : Aurélien Regat-Barrel, Laurent Gomila
Une classe est déclarée dans un fichier header (extension .h, .hpp. ou encore .hxx) dont l'inclusion multiple est protégée grâce à des directives du préprocesseur :
maclasse.h
#ifndef MACLASSE_H
#define MACLASSE_H

class MaClasse
{
public:
    void Fonction();
};

#endif
Le corps de la classe est généralement placé dans un fichier séparé dont l'extension varie (.cpp, .cxx, .C). Ce fichier contient le code compilable :
maclasse.cpp
#include "maclasse.h"

void MaClasse::Fonction()
{
    // implémentation de la fonction
}
Pour utiliser une classe dans d'autres fichiers .cpp, il suffira d'inclure l'en-tête qui la déclare ; c'est l'éditeur de liens qui se chargera de trouver tout seul où se trouve le corps des fonctions (ie. vous n'avez pas à vous en préoccuper). Certains seront parfois tentés d'inclure un fichier .cpp : c'est une erreur et cela ne doit jamais être fait (il en résulterait plusieurs corps pour une même fonction, par exemple).
autreclasse.cpp

#include "autreclasse.h"
#include "maclasse.h"

void AutreClasse::Fonction()
{
    MaClasse M;
    M.Fonction();
}
Le corps de certaines fonctions peut figurer dans le header, en particulier pour les fonctions inline et dans le cas de fonctions/classes templates (lire à ce sujet Pourquoi mes templates ne sont-ils pas reconnus à l'édition des liens ? ). Attention à ne pas oublier le mot clé inline si vous placez le corps de fonctions dans un header ailleurs que dans la déclaration d'une classe :
maclasse.h
#ifndef MACLASSE_H
#define MACLASSE_H

class MaClasse
{
public:
    void FonctionInline()
    {
        // placée dans la déclaration de la classe
        // cette fonction est considérée en ligne sans
        // que le mot clé inline ne figure
    }
};

// placée en dehors de la déclaration de la classe, le mot clé inline
// est indispensable pour ne pas provoquer des définitions multiples
// de la fonction et donc des erreurs à l'édition de liens
inline void FonctionInline2()
{
}
#endif
Soyez aussi vigilants au respect de la casse dans l'inclusion d'une header. Si vous incluez maclasse.h, veillez à bien nommer votre fichier header maclasse.h et non MaClasse.h car certains systèmes font la distinction de casse dans le nom des fichiers et cela s'applique aussi lors de leur inclusion dans un fichier C++.


Qu'est-ce "this" ?
Créé le 20/04/2003[haut]
auteur : LFE
this est un pointeur créé par défaut et qui désigne l'objet lui-même. A noter que this est un pointeur non modifiable, l'adresse pointée ne peut être changée (ce qui est d'ailleurs parfaitement logique).


Comment puis-je empêcher les autres programmeurs de violer l'encapsulation en accédant aux membres privés de mes classes ?
Créé le 10/02/2004[haut]
auteur : Marshall Cline
Le jeu n'en vaut pas la chandelle, l'encapsulation est faite pour le code, pas pour les gens.

Ce n'est pas violer l'encapsulation pour un programmeur que de voir les parties privées et/ou protégées de vos classes, tant qu'il n'écrit pas de code qui dépende d'une façon ou d'une autre de ce qu'il voit. En d'autres termes, l'encapsulation n'empêche pas les gens de découvrir comment est constituée une classe; cela empêche que le code que l'on écrit ne soit dépendant de l'intérieur de la classe. La société qui vous emploie ne doit pas payer un "contrat de maintenance" pour entretenir la matière grise qui se trouve entre vos 2 oreilles, mais elle doit payer pour entretenir le code qui sort de vos doigts. Ce que vous savez en tant que personne n'augmente pas le coût de maintenance, partant du principe que le code que vous écrivez dépend de l'interface plus que de l'implémentation.

D'un autre coté, ce n'est rarement, voire jamais un problème. Je ne connais aucun programmeur qui ait intentionnellement essayé d'accéder aux parties privées d'une classe. "Mon avis dans un tel cas de figure serait de changer le programmeur et non le code" (James Kanze, avec son autorisation)


Comment dériver une classe à partir d'une autre ?
Créé le 09/10/2003[haut]
auteur : LFE
Pour dériver une classe à partir d'une autre, il suffit de faire suivre la déclaration de la classe dérivée de : suivi d'un modificateur d'accès et du nom de la classe mère.
class mere
{
// ...
} 

class fille : public mere
{
// ...
}
L'héritage peut être public (public), privé (private) ou protégé (protected).
Si l'héritage est public, les membres de la classe mère garderont leur accès, à savoir les membres publics restent publics et les protégés restent protégés.
Si l'héritage est protected, les membres publics de la classe mère deviendront protégés dans la classe fille, et les protégés resteront tels quels.
Si l'héritage est private, tous les membres de la classe mère, qu'ils aient été publics ou protégés deviennent privés.
Les membres privés de la classe mère sont inaccessibles depuis la classe fille, quelque soit le type d'héritage.


Comment faire pour empêcher de créer plus d'une instance d'une classe ?
Créé le 22/11/2004[haut]
auteurs : Laurent Gomila, Aurélien Regat-Barrel
Pour limiter le nombre d'instances d'une classe, on utilise le design pattern du singleton. Il permet de contrôler le nombre d'instances d'une classe (en général une seule). Voici un exemple typique d'implémentation en C++ :
// fichier Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

class Singleton 
{ 
public : 
    // Fonction permettant de récupérer l'instance unique de notre classe 
    static Singleton & GetInstance();

private :
    // On empêche la création directe d'objets Singleton en mettant son constructeur par défaut privé
    // Ainsi on oblige l'utilisateur à passer par GetInstance
    Singleton() {} 

    // On empêche la recopie d'objets Singleton en mettant le constructeur par copie
    // et l'opérateur d'affectation en privé
    // On peut même ne pas les implémenter, c'est encore mieux
    Singleton( const Singleton & ); 
    Singleton & operator =( const Singleton & ); 
}; 

#endif
// fichier Singleton.cpp
#include "Singleton.h"

Singleton & Singleton::GetInstance()
{
    static Singleton instance; // instance unique cachée dans la fonction. Ne pas oublier le static !
    return instance;
}
// exemple d'utilisation
#include "Singleton.h"

int main() 
{ 
    // Essai : on tente de créer un Singleton 
    Singleton erreur; // Ne compile pas - le constructeur par défaut n'est pas accessible 

    // Récupération de l'instance unique de la classe 
    Singleton & s = Singleton::GetInstance(); 
}
Bien sûr, ce n'est qu'un exemple d'implémentation. Selon les besoins on pourra coder notre singleton différemment. Par exemple, GetInstance() peut renvoyer un pointeur fraîchement alloué, ce qui peut être justifié si l'on veut disposer de plus d'une instance, ou si l'on veut contrôler le moment de sa destruction (dans le cas de dépendances entre plusieurs singletons par exemple). A chaque appel un compteur interne est incrémenté et au delà d'un certain nombre d'instances la fonction refuse d'en allouer de nouvelles. Il ne faut en revanche pas oublier de libérer les pointeurs ainsi obtenus.
On peut également modifier le code pour le rendre thread-safe en ajoutant une protection de type verrou dans GetInstance() si l'on fait du multithreading.


Que signifient public, private et protected ?
Mise à jour le 17/10/2005[haut]
auteurs : LFE, Laurent Gomila
Un membre déclaré public dans une classe peut être accédé par toutes les autres classes et fonctions.
Un membre déclaré protected dans une classe ne peut être accédé que par les autres membres de cette même classe ainsi que par les membres des classes dérivées.
Un membre déclaré private dans une classe ne peut être accédé que par les autres membres de cette même classe.

Ces mots-clés permettent également de modifier la visibilité des membres dans la classe dérivée lors d'un héritage :

Héritage
Accès aux données public protected private
public public protected private
protected protected protected private
private interdit interdit interdit
Exemple :

class A
{
public :

    int x;

protected :

    int y;
    
private :

    int z;
};

class B : private A
{
    // x et y sont ici en accès privé, et z est innaccessible
}

Quelle est la différence entre class et struct ?
Créé le 17/10/2005[haut]
auteurs : Laurent Gomila, Aurélien Regat-Barrel
Contrairement à ce que l'on pourrait penser, les classes (class) et les structures (struct) sont équivalentes en C++.

Seules trois différences mineures existent :

1. La visibilité par défaut est public pour les structures, private pour les classes.

struct A
{
    int a;
    
private :

    int b;
};

class B
{
    int b;
    
public :

    int a;
};

// Ici, A et B sont équivalents
2. Le mode d'héritage par défaut est public pour les structures, private pour les classes.

class Base {}; // class ou struct, peu importe

class A1 : public Base
{
};

struct A2 : Base
{
};

class B1 : Base
{
};

struct B2 : private Base
{
}; 

// Ici, A1 et A2 sont équivalents, ainsi que B1 et B2
3. L'utilisation en tant que type template est interdite pour struct.

// OK
template <class T>
struct A
{
};

// ERREUR
template <struct T>
class B
{
};
A noter que la norme permet même d'effectuer une déclaration anticipée de classe via le mot-clé struct, et inversement. Cependant, certains compilateurs ne l'acceptent pas.

Fichier.h

struct A; // déclaration anticipée en tant que struct

A * Exemple();
Fichier.cpp

class A // déclaration en tant que class
{
};

A * Exemple()
{
    return new A;
}
Classes et structures sont donc presque équivalentes, cependant on adopte souvent cette convention : on utilise struct pour les structures type C (pas de fonction membre, d'héritage, de constructeurs, ...) et class pour tout le reste.


Comment créer 2 classes qui font référence l'une à l'autre ?
Créé le 20/04/2003[haut]
auteurs : pipin, Laurent Gomila
Il arrive souvent qu'une classe A contienne un attribut (ou un argument de fonction) de type classe B et que la classe B contienne elle aussi un attribut (ou un argument de fonction) de type classe A. Mais si l'on inclue A.h dans B.h et vice-versa, on se retrouve avec un problème d'inclusions cycliques.
La solution à ce problème est d'utiliser des déclarations anticipées (forward declaration). Au lieu d'inclure l'en-tête définissant une classe, on va simplement déclarer celle-ci pour indiquer au compilateur qu'elle existe. Cela marche car tant qu'on n'utilise qu'un pointeur ou une référence, le compilateur n'a pas besoin de connaître en détail le contenu de la classe. Il a juste besoin de savoir qu'elle existe. Par contre au moment d'utiliser celle-ci (appel d'une fonction membre par exemple) il faudra bien avoir inclus son en-tête, mais ce sera fait dans le .cpp et non plus dans le .h, ce qui élimine le problème d'inclusion cyclique.
A.h
class B; 

class A 
{ 
    B* PtrB; 
};
A.cpp
#include "A.h" 
#include "B.h" 

// ...
B.h
#include "A.h" 

class B 
{ 
    A a; 
};
B.cpp
#include "B.h" 

// ...
De manière générale les déclarations anticipées sont à utiliser autant que possible, à savoir dès qu'on déclare dans une classe un pointeur ou une référence sur une autre classe. En effet, cela permet aussi de limiter les dépendances entre les fichiers et de réduire considérablement le temps de compilation.


Que signifie objet.fonction1().fonction2() ?
Créé le 10/02/2004[haut]
auteur : Marshall Cline
C'est l'enchaînement des appels de ces fonctions membres, c'est pourquoi cela s'appelle le chaînage des fonctions.

La première chose qui est exécutée est objet.methode1(). Cela renvoie un objet ou une référence sur un objet (par ex. methode1() peut se terminer en renvoyant *this, ou n'importe quel autre objet). Appelons cet objet retourné "ObjetB". Ensuite, ObjetB devient l'objet auquel est appliqué methode2().

L'utilisation la plus courante du chaînage de fonctions est l'injection / extraction vers les flux standards.
cout << x << y ;
fonctionne parce que
cout << x
est une fonction qui retourne cout.


Comment effectuer la copie d'objets polymorphes ?
Mise à jour le 17/10/2005[haut]
auteurs : Laurent Gomila, JolyLoic
Parfois, lorsqu'on manipule des objets polymorphes (ie. des classes dérivées via un pointeur sur leur classe de base), on voudrait connaître leur type réel, par exemple pour les copier. Malheureusement, conceptuellement ce n'est souvent pas la meilleure solution, et c'est parfois lourd à gérer. Le moyen le plus efficace de procéder à une copie d'objets polymorphes est sans doute d'utiliser le design pattern Clone :

// Définition de notre classe de base 
struct Base
{ 
    virtual ~Base() {}
    virtual Base* Clone() const = 0;
};

// Une classe dérivée de Base
struct Derivee1 : public Base
{
    virtual Base* Clone() const
    {
         return new Derivee1(*this);
    }
};

// Une autre classe dérivée de Base
struct Derivee2 : public Base
{
    virtual Base* Clone() const
    {
         return new Derivee2(*this);
    }
};

// Utilisation 
Base* Obj1 = new Derivee1;
Base* Obj2 = new Derivee2;

Base* Copy1 = Obj1->Clone(); // Copy1 pointe sur un objet de type Derivee1
Base* Copy2 = Obj2->Clone(); // Copy2 pointe sur un objet de type Derivee2

delete Obj1;
delete Obj2;
delete Copy1;
delete Copy2;
Le code précédent est simple et fonctionne parfaitement. Une variante plus subtile existe : elle consiste à utiliser comme type de retour de Clone() la classe dans laquelle la fonction membre est définie au lieu d'utiliser la classe de base :

struct Derivee1 : public Base
{
    virtual Derivee1* Clone() const
    {
         return new Derivee1(*this);
    }
};
Ce code nécessite que les retours covariants soient supportés par le compilateur. Voir, pour plus de précisions, Qu'est-ce qu'un type de retour covariant ?.


Qu'est-ce qu'une classe abstraite ?
Mise à jour le 22/10/2004[haut]
auteurs : LFE, Aurélien Regat-Barrel
Une classe abstraite est une classe qui possède au moins une fonction membre virtuelle pure (lire Qu'est-ce qu'une fonction virtuelle pure ?). Cette fonction devant être supplantée, ce type de classe ne peut pas être instancié, et est donc destiné à être dérivé pour être spécialisé. La ou les classes filles doivent supplanter l'ensemble des fonctions virtuelles pures de leurs parents. On dit alors que les classes filles concrétisent la classe abstraite.
class Bienvenue // classe abstraite
{
public:
    // le "= 0" à la fin indique que c'est
    // une fonction virtuelle pure
    virtual void Message() = 0;
};

class BienvenueEnFrancais : public Bienvenue
{
public:
    void Message()
    {
        std::cout << "Bienvenue !\n";
    }
};

class BienvenueEnAnglais : public Bienvenue
{
public:
    void Message()
    {
        std::cout << "Welcome !\n";
    }
};
lien : Qu'est-ce qu'une fonction virtuelle pure ?

Quelle forme canonique adopter en fonction de la sémantique de la classe ?
Créé le 15/10/2009[haut]
auteur : Medinoc
Lorsqu'une classe doit gérer de la mémoire, il est d'usage de la mettre sous une forme que l'on appelle forme canonique. Mais il existe plusieurs variantes de la forme canonique, selon que la classe soit copiable, modifiable, swappable...

Classe non-copiable, non-assignable, mais swappable :

  • constructeur ;
  • constructeur par copie : privé, non-défini ;
  • operator= : privé, non-défini ;
  • swap() + spécialisation de std::swap ;
  • destructeur.
Classe copiable, mais immuable (non-assignable, non-swappable)

  • constructeur ;
  • constructeur par copie ;
  • operator= : privé, non-défini ;
  • destructeur.
Classe pleinement copiable, assignable, swappable :

  • constructeur ;
  • constructeur par copie ;
  • operator= : utilise idiome copy-and-swap ;
  • swap() + spécialisation de std::swap ;
  • destructeur.
De plus, le destructeur n'a pas forcément à être virtuel, cela dépend des cas. Si l'héritage est public et que la classe de base va servir en tant qu'interface alors le destructeur doit être virtuel. Par contre si l'héritage est protégé/privé et qu'il sert à réutiliser du code sans notion d'interface et de polymorphisme, alors le destructeur de la classe de base doit être protégé et non-virtuel.
Enfin pour les constructeurs, il est souvent préconisé de les faire protégés pour toutes les classes non-terminales, seules les classes terminales doivent être instanciables.


Qu'est-ce que la forme canonique orthodoxe de Coplien ?
Créé le 15/10/2009[haut]
auteur : 3DArchi
La forme canonique orthodoxe de Coplien a été définie par Coplien pour les classes à faq sémantique de valeur . Elle précise que si une classe doit définir une des quatre méthodes suivantes alors elle doit les définir toutes :

  • un constructeur par défaut : CMyClass() ;
  • un constructeur par copie : CMyClass(CMyClass const &) ;
  • l'opérateur d'affectation : CMyClass&operator=(CMyClass const &) ;
  • le destructeur : ~CMyClass().

Quand dois-je définir un constructeur par défaut ?
Créé le 15/10/2009[haut]
auteur : 3DArchi
Le constructeur par défaut est le constructeur qui ne prend aucun argument ou dont les arguments ont une valeur par défaut :
class CMyClass
{
   public:
   CMyClass();// constructeur par défaut
};
ou
class CMyClass
{
   public:
   CMyClass(T1 param1=def_value, T2 param2=T2());// constructeur par défaut
};
Si la classe n'en définit pas, alors le compilateur en propose un implicitement. Celui-ci appelle le constructeur par défaut des classes de base et le constructeur par défaut des membres. Pour les types POD, les valeurs sont laissées non initialisées.
Une exception : si la classe définit un autre constructeur (constructeur avec paramètres sans valeur par défaut ou constructeur par copie), alors le compilateur ne génère pas de constructeur par défaut. Dans ce cas, si cela a un sens pour la classe, un constructeur par défaut doit alors être explicitement défini.


Quand dois-je définir un constructeur par copie ?
Créé le 15/10/2009[haut]
auteur : 3DArchi
Le constructeur par copie se base sur un autre objet du même type pour construire l'objet en cours :
class CMyClass
{
public:
   CMyClass(CMyClass const&);// constructeur par copie
};
Si la classe ne définit pas de constructeur par copie, le compilateur génère un constructeur de copie implicitement. Celui-ci appelle le constructeur par copie des classes parents et le constructeur par copie des membres. Ceci peut être désactivé faq  en rendant la classe non copiable .
Si on souhaite donner une sémantique de copie et que le constructeur par copie ne convient pas (par exemple, parce qu'une ressource est gérée), alors il faut définir un constructeur par copie.
Dans le cadre d'une utilisation polymorphe, on peut vouloir définir un constructeur par copie pour permettre le clonage. Celui-ci est alors protégé car la copie n'a de sens que dans ce cadre. La fonction Clone est, elle, publique.


Quand dois-je définir l'opérateur d'affectation ?
Créé le 15/10/2009[haut]
auteur : 3DArchi
L'opérateur d'affectation permet de copier la valeur d'un objet dans un autre :
class CMyClass
{
public:
   CMyClass& operator=(CMyClass const&);// opérateur d'affectation
};
Si la classe ne définit pas d'opérateur d'affectation, le compilateur en génère un implicitement. Celui-ci appelle l'opérateur d'affectation des membres.
A noter que si la classe suit faq une sémantique d'entité , la définition d'un opérateur d'affectation a de grandes chances d'être une erreur de conception. Il faut alors le déclarer privé sans le définir de façon à interdire son utilisation.

lien : faq Quand est-ce qu'une classe a une sémantique de d'entité ?
lien : faq Qu'est-ce qu'une classe copiable ?
lien : faq Comment écrire un opérateur d'affectation correct ?

Quand dois-je définir un destructeur ?
Créé le 15/10/2009[haut]
auteur : 3DArchi
Le destructeur est appelé pour libérer les ressources acquises par un objet lorsque l'espace occupé par celui-ci doit être libéré :
class CMyClass
{
public:
   ~CMyClass();// destructeur
};
Si la classe ne contient pas de destructeur par défaut, alors le compilateur en génère un implicitement. Celui-ci va appeler les destructeurs des différents membres puis celui des classes parents.
Si on doit utiliser la classe comme base dans un héritage pour une utilisation polymorphe (utilisation via une référence ou un pointeur sur la base), alors la classe de base doit définir un destructeur virtuel.

lien : faq Pourquoi le destructeur d'une classe de base doit être public et virtuel ou protégé et non virtuel ?

Que conclure de la forme canonique orthodoxe de Coplien ?
Créé le 15/10/2009[haut]
auteur : 3DArchi
  • Si une des quatres méthodes ci-dessus a été définie de façon non triviale, alors il est fortement probable que les trois autres doivent être définies.
  • Si une classe doit servir de base pour une utilisation polymorphe, alors le destructeur doit être déclaré comme virtuel. A noter qu'il est fort probable que faq  la classe soit alors non copiable .
  • La sémantique d'une classe va imposer la politique de définition des méthodes ( faq  Quelle forme canonique adopter en fonction de la sémantique de la classe ? ).
  • Si la définition implicite convient, compte tenu des éléments ci-dessus, alors il n'est pas nécessaire de définir explicitement ces méthodes. Et les définir peut avoir des impacts négatifs : il existe des outils d'analyse de code (et peut-être des futures évolutions du langage) qui vérifient justement que l'on a respecté ces règles. Définir une fonction ne faisant rien peut alors mettre en échec ces outils.


Consultez les autres F.A.Q.


Valid XHTML 1.0 TransitionalValid CSS!

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2008 Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.