Chapitre 10. Identification dynamique des types

Table des matières
10.1. Identification dynamique des types
10.2. Transtypages C++

Le C++ est un langage fortement typé. Malgré cela, il se peut que le type exact d'un objet soit inconnu à cause de l'héritage. Par exemple, si un objet est considéré comme un objet d'une classe de base de sa véritable classe, on ne peut pas déterminer a priori quelle est sa véritable nature.

Cependant, les objets polymorphiques (qui, rappelons-le, sont des objets disposant de méthodes virtuelles) conservent des informations sur leur type dynamique, à savoir leur véritable nature. En effet, lors de l'appel des méthodes virtuelles, la méthode appelée est la méthode de la véritable classe de l'objet.

Il est possible d'utiliser cette propriété pour mettre en place un mécanisme permettant d'identifier le type dynamique des objets, mais cette manière de procéder n'est pas portable. Le C++ fournit donc un mécanisme standard permettant de manipuler les informations de type des objets polymorphiques. Ce mécanisme prend en charge l'identification dynamique des types et la vérification de la validité des transtypages dans le cadre de la dérivation.

10.1. Identification dynamique des types

10.1.1. L'opérateur typeid

Le C++ fournit l'opérateur typeid, qui permet de récupérer les informations de type des expressions. Sa syntaxe est la suivante :

typeid(expression)
expression est l'expression dont il faut déterminer le type.

Le résultat de l'opérateur typeid est une référence sur un objet constant de classe type_info. Cette classe sera décrite dans la Section 10.1.2.

Les informations de type récupérées sont les informations de type statique pour les types non polymorphiques. Cela signifie que l'objet renvoyé par typeid caractérisera le type de l'expression fournie en paramètre, que cette expression soit un sous-objet d'un objet plus dérivé ou non. En revanche, pour les types polymorphiques, si le type ne peut pas être déterminé statiquement (c'est-à-dire à la compilation), une détermination dynamique (c'est-à-dire à l'exécution) du type a lieu, et l'objet de classe type_info renvoyé décrit le vrai type de l'expression (même si elle représente un sous-objet d'un objet d'une classe dérivée). Cette situation peut arriver lorsqu'on manipule un objet à l'aide d'un pointeur ou d'une référence sur une classe de base de la classe de cet objet.

Exemple 10-1. Opérateur typeid

#include <typeinfo>

using namespace std;

class Base
{
public:
    virtual ~Base(void);   // Il faut une fonction virtuelle
                           // pour avoir du polymorphisme.
};

Base::~Base(void)
{
    return ;
}

class Derivee : public Base
{
public:
    virtual ~Derivee(void);
};

Derivee::~Derivee(void)
{
    return ;
}

int main(void)
{
    Derivee* pd = new Derivee;
    Base* pb = pd;
    const type_info &t1=typeid(*pd);  // t1 qualifie le type de *pd.
    const type_info &t2=typeid(*pb);  // t2 qualifie le type de *pb.
    return 0 ;
}

Les objets t1 et t2 sont égaux, puisqu'ils qualifient tous les deux le même type (à savoir, la classe Derivee). t2 ne contient pas les informations de type de la classe Base, parce que le vrai type de l'objet pointé par pb est la classe Derivee.

Note : Notez que la classe type_info est définie dans l'espace de nommage std::, réservé à la bibliothèque standard C++, dans l'en-tête typeinfo. Par conséquent, son nom doit être précédé du préfixe std::. Vous pouvez vous passer de ce préfixe en important les définitions de l'espace de nommage de la bibliothèque standard à l'aide d'une directive using. Vous trouverez de plus amples renseignements sur les espaces de nommage dans le Chapitre 11.

On fera bien attention à déréférencer les pointeurs, car sinon, on obtient les informations de type sur ce pointeur, pas sur l'objet pointé. Si le pointeur déréférencé est le pointeur nul, l'opérateur typeid lance une exception dont l'objet est une instance de la classe bad_typeid. Cette classe est définie comme suit dans l'en-tête typeinfo :

class bad_typeid : public logic
{
public:
    bad_typeid(const char * what_arg) : logic(what_arg)
    {
        return ;
    }

    void raise(void)
    {
        handle_raise();
        throw *this;
    }
};

10.1.2. La classe type_info

Les informations de type sont enregistrées dans des objets de la classe type_info prédéfinie par le langage. Cette classe est déclarée dans l'en-tête typeinfo de la manière suivante :

class type_info
{
public:
    virtual ~type_info();
    bool operator==(const type_info &rhs) const;
    bool operator!=(const type_info &rhs) const;
    bool before(const type_info &rhs) const;
    const char *name() const;
private:
    type_info(const type_info &rhs);
    type_info &operator=(const type_info &rhs);
};

Les objets de la classe type_info ne peuvent pas être copiés, puisque l'opérateur d'affectation et le constructeur de copie sont tous les deux déclarés private. Par conséquent, le seul moyen de générer un objet de la classe type_info est d'utiliser l'opérateur typeid.

Les opérateurs de comparaison permettent de tester l'égalité et la différence de deux objets type_info, ce qui revient exactement à comparer les types des expressions.

Les objets type_info contiennent des informations sur les types sous la forme de chaînes de caractères. Une de ces chaînes représente le type sous une forme lisible par un être humain, et une autre sous une forme plus appropriée pour le traitement des types. Le format de ces chaînes de caractères n'est pas précisé et peut varier d'une implémentation à une autre. Il est possible de récupérer le nom lisible du type à l'aide de la méthode name. La valeur renvoyée est un pointeur sur une chaîne de caractères. On ne doit pas libérer la mémoire utilisée pour stocker cette chaîne de caractères.

La méthode before permet de déterminer un ordre dans les différents types appartenant à la même hiérarchie de classes, en se basant sur les propriétés d'héritage. L'utilisation de cette méthode est toutefois difficile, puisque l'ordre entre les différentes classes n'est pas fixé et peut dépendre de l'implémentation.