User Tools

Site Tools


in204:cpp:syntax:class:deriving:constructor

Constructeurs & Classes dérivées

Une classe dérivée :

  • hérite de l'ensemble des champs et fonctions membres de la classe de base,
  • définit des champs et des fonctions membres supplémentaires.

Lors de la création d'un nouvel objet, nous avons deux phases :

  • La première phase consiste à allouer la mémoire. Dans le cas d'une classe dérivée, la mémoire allouée est calculée comme étant la mémoire nécessaire pour le ou les classes de bases ainsi que la mémoire nécessaire à allouer l'ensemble des champs et tables de méthodes nécessaires à la création de la classe de base.
  • La seconde phase consiste à initialiser l'ensemble des champs en effectuant des appels aux constructeurs.

Pouvons nous hériter des constructeurs ?

Les constructeurs définis pour chacune des classes de base ne disposent pas des informations nécessaires pour pouvoir initialiser les champs dans la classe étendue.

  • Appeler le constructeur d'une classe de base ne permet pas de garantir que l'ensemble des extensions définies par la classe qui hérite de la classe de base puisse être correctement initialisé.

Il est donc nécessaire de définir un nouveau constructeur qui va :

  1. initialiser la classe ou les classes de bases en appelant le constructeur. Si aucun constructeur n'est appelé explicitement, c'est le constructeur par défaut de la classe de base qui est appelée 1).
  2. initialiser les champs définis dans la classe dérivée.

Syntaxe d'un constructeur dans une classe dérivée

La syntaxe d'un constructeur d'une classe dérivée appelant les constructeurs de la classe de base est la suivante :

 
class Cloth
{
public:
    enum Size { S, M, L, XL, XXL, XXXL }; 
 
private:
    Size m_size;
 
public:
    Size getSize() const { return m_size; }
 
protected:
    Cloth(): m_size(Cloth::S);
    explicit Cloth(Size theSize): m_size(theSize)
    {}
    Cloth(const ClothBase& theSource): 
        m_size(theSource.theSize)
    {}
};
 
public class Pant: Cloth
{
private:
    unsigned m_length;
public:
    Pant(Cloth::Size theSize, unsigned theLength):
        Cloth(theSize), m_length(theLength)      
    {}
    Pant(unsigned theLength):
        m_length(theLength)      
    {}
    Pant(const Pant& anotherPant):
        Cloth(anotherPant), m_lentgh(anotherPant.m_length)
    {}   
};

Dans le code précédent, Pant définit un nouveau constructeur Pant(Cloth::Size, unsigned) qui va appelé tout d'abord le constructeur Cloth(Cloth::Size) pour initialiser la classe de base et ensuite procéder à l'initialisation du champ défini dans Pant avec la valeur passée au paramètre theLength.

Si nous souhaitons inversé l'odre d'initialisation, nous aurions du écrire :

    Pant(Cloth::Size theSize, unsigned theLength):
        m_length(theLength), Cloth(theSize)   
    {}

Dans ce cas de figure, la première opération consiste à initialisé le champs défini dans Pant et ensuite d'appelé le constructeur Cloth(Cloth::Size) pour initialiser la classe de base. Ceci peut avoir une importance quand l'initialisation d'un champ ou d'une classe requiert que la valeur définie pour un autre champ ou une autre classe soit déjà effectuée.

Considérons désormais la déclaration du constructeur Pant(unsigned). Ce constructeur se contente d'initialiser le champ m_length. Il n'y a pas d'appel à un constructeur de la classe de base.

    Pant(unsigned theLength):
        m_length(theLength)      
    {}

Mais il est nécessaire d'initialiser intégralement les extensions mais aussi la classe de base. Si aucun constructeur n'est appelé, C++ initialise la classe de base en appelant le constructeur par défaut. En fait, le code précédent est équivalent au code suivant :

    Pant(unsigned theLength):
        m_length(theLength), Cloth()   
    {}

Ceci permet de garantir que l'ensemble des champs est bien initialisé par le constructeur.

Le constructeur par défaut

Le constructeur par défaut est le constructeur qui ne prend aucun argument. Les règles d'écriture dans une classe dérivée du constructeur par défaut sont identifiques à celles valables pour des constructeurs spécialisés pour une classe dérivée.

Cependant en l'absence de constructeurs spécialisé et d'une définition explicite du constructeur par défaut dans une classe dérivée, C++ définit un constructeur par défaut implicite qui est généré et a comme caractéristique les caractéristiques suivantes :

  • Ce constructeur est public.
  • Ce constructeur appelle pour chacun des champs de la classe et des classes de bases soit le constructeur, soit procède à l'affectation de la valeur par défaut, soit procède à l'initialisation par défaut du type (qui peut consister justement à ne rien faire).
  • le corps du constructeur est vide.
class MyClass: 
  public BaseClass, 
  private ImplementationClass
{
    private:
        int value;
    public:
        MyClass(): BaseClass(), ImplementationClass(), 
            value() // Correspond au constructeur par défaut généré automatiquement
                    // en C++.
        {}
}; 

Le constructeur de recopie

Le constructeur de recopie est un constructeur qui a pour signature ClassName(const ClassName&)ClassName est le nom de la classe.

Si aucun constructeur de recopie n'est défini pour une classe héritant de plusieurs classes, un constructeur de recopie est automatiquement généré en C++.

  • Ce constructeur est public,
  • Ce constructeur effectue pour chacun des champs de la classe une opération de recopie. Si le type du champ est une classe, il appelle le constructeur de recopie défini par le type du champ, si le type du champ est un type simple, il effectue une copie mémoire du contenu du champ. Pour chacune des classes de bases, il appele le constructeur de recopie définie par cette classe de base.
  • Le corps du constructeur est vide.
class MyClass: 
  public BaseClass, 
  private ImplementationClass
{
    private:
        int value;
    public:
        MyClass(const MyClass& anotherClass): 
            BaseClass(anotherClass), 
            ImplementationClass(anotherClass), 
            value(anotherClass.value) 
                // Correspond au constructeur de recopie généré automatiquement
                // en C++.
        {}
}; 

Comme MyClass hérite de BaseClass et de ImplementationClass, la conversion de anotherClass qui a pour type const MyClass& en une référence à un objet de type BaseClass ou Implementation est toujours possible puisque que MyClass hérite de l'ensemble des champs et fonctions membres de BaseClass et de ImplementationClass et par extension pour toujours se comporter comme étant un objet ayant pour type BaseClass ou pour type ImplementationClass.

1)
Attention, si aucun constructeur par défaut n'est défini pour la classe de base, une erreur de compilation se produira
in204/cpp/syntax/class/deriving/constructor.txt · Last modified: 2022/11/18 10:51 (external edit)