Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Héritage : comment est-ce implémenter ?

Dans ce chapitre, nous allons voir comment l’héritage et le polymorphisme par héritage fonctionne derrière la boutique. Nous allons aborder la notion de table de méthodes virtuelles qui permet d’appeler les bonnes méthodes sur les bons objets : parmis les animaux, les chats miaulent et les crapauds croassent.

Exemple de déclaration

#include <iostream>
#include <vector>

using namespace std;

class Animal
{
private:
    int legs;
public:
    virtual void parler()
    {
        cout << "je sais pas parler car je suis un animal générique" << endl;
    }
};

class Chat : public Animal
{
private:
    int vibrissae = 6;
public:
    void parler()
    {
        cout << "miaou" << vibrissae << endl;
        vibrissae++;
    }
};

class Crapaud : public Animal
{
public:
    void parler()
    {
        cout << "crooaaaa" << endl;
    }
};

Programme qui ne fonctionne pas bien

    int main()
    {
        vector<Animal> S;

        S.push_back(Chat());
        S.push_back(Crapaud());

        for (Animal &a : S)
        {
            a.parler();
        }
    }

Sans pointeurs, pas possible de faire du polymorphisme de sous-typage. En effet, quand on met dans une zone mémoire pour un Animal, ce qui dépasse (propre à Cat ou Frog) est juste rogné. Donc on ne peut pas appliquer des méthodes de Cat qui ne vont pas trouver les données propres à un chat. Bref, on a vraiment que des objets de la classe Animal.

Programme qui fonctionne mais bon…

En C++, on peut utiliser des pointeurs à l’ancienne, i.e. des pointeurs bruts et ça marche.

int main()
{
    vector<Animal*> S;

    S.push_back(new Chat());
    S.push_back(new Crapaud());

    for (Animal*& a : S)
    {
        a->parler();
    }
}

Programme chouette

On peut aussi écrire ce programme avec des pointeurs intelligents.

#include <memory>

int main()
{
    vector< unique_ptr<Animal> > S;

    S.push_back(unique_ptr<Animal>(new Chat()));
    S.push_back(unique_ptr<Animal>(new Crapaud()));

    for (unique_ptr<Animal>& a : S)
    {
        a->parler();
    }
}

Pareil en Rust on a besoin de pointeurs

trait Animal {
    fn parler(&self);
}
struct Chat {
    vibrissae: i8,
}

impl Animal for Chat {
    fn parler(&self) {
        println!("Hello j'ai {} vibrisses.", self.vibrissae)
    }
}

struct Crapaud;

impl Animal for Crapaud {
    fn parler(&self) {
        println!("Crrooooaaa")
    }
}

fn main() {
    let mut v: Vec<Box<dyn Animal>> = Vec::new();
    v.push(Box::new(Chat { vibrissae: 6 }));
    v.push(Box::new(Crapaud {}));
    for a in v {
        a.parler()
    }
}

Virtual method table

C++ creates a table for each class containing a pointer for each method declared as virtual. It is called a virtual method table (or vtable, or VMT). Then each object starts with a pointer to the appropriate virtual method table.

Virtual method tables are built at compile-time and solely contains virtual methods. Non-virtual methods are just called directly, without any look-up in a vtable.

#include <stdio.h>
#include <stdlib.h>

typedef struct Animal Animal;

typedef struct
{
    void (*parler)(Animal *);
    void (*dormir)(Animal *);
} AnimalVTable;

/***** classe Animal */

struct Animal
{
    const AnimalVTable *vtable;
    int legs;
};

void Animal_parler(Animal *a)
{
    printf("je sais pas parler car je suis un animal générique\n");
}

void Animal_dormir(Animal *a)
{
    printf("je sais pas dormir car je suis un animal générique\n");
}

const AnimalVTable Animal_vtable = {.parler = Animal_parler, .dormir = Animal_dormir};


/******* classe fille Chat ***************/

typedef struct
{
    Animal base;
    int vibrissae;
} Chat;

void Chat_parler(Chat *this)
{
    printf("Miaou et j'ai %i vibrisses !\n", this->vibrissae);
}

void Chat_dormir(Chat *this)
{
    printf("Le chat ronronne en dormant.\n");
}

const AnimalVTable Chat_vtable = {
    .parler = (void (*)(Animal *))Chat_parler,
    .dormir = (void (*)(Animal *))Chat_dormir};

Chat *Chat_new(void)
{
    Chat *c = malloc(sizeof(Chat));
    c->base.vtable = &Chat_vtable;
    return c;
}

/*************** Classe fille Crapaud ****/

typedef struct
{
    Animal base;
    int quantite_de_baves;
} Crapaud;

void Crapaud_parler(Crapaud *this)
{
    printf("Croa croa !\n");
}

void Crapaud_dormir(Crapaud *this)
{
    printf("Le crapaud s'enfouit dans la boue avec ses %i jambes pour dormir.\n", this->base.legs);
}

const AnimalVTable Crapaud_vtable = {
    .parler = (void (*)(Animal *))Crapaud_parler,
    .dormir = (void (*)(Animal *))Crapaud_dormir};

Crapaud *Crapaud_new(void)
{
    Crapaud *c = malloc(sizeof(Crapaud));
    c->base.vtable = &Crapaud_vtable;
    return c;
}

/*************** programme principal qui illustre le polymorphisme de type */

int main(void)
{
    Animal *animaux[3];

    animaux[0] = (Animal *)Chat_new();
    animaux[1] = (Animal *)Crapaud_new();
    animaux[2] = (Animal *)Chat_new();

    for (int i = 0; i < 3; i++)
    {
        animaux[i]->vtable->parler(animaux[i]);
        animaux[i]->vtable->dormir(animaux[i]);
        printf("\n");
    }

    free(animaux[0]);
    free(animaux[1]);
    free(animaux[2]);

    return 0;
}