Différences entre static_cast et dynamic_cast en C++

Les conversions de types explicites en C++ sont essentielles pour la manipulation sûre des données. Deux opérateurs de transtypage couramment utilisés sont static_cast et dynamic_cast. Bien qu'ils puissent tous deux être utilisés pour convertir des pointeurs et des références au sein d'une hiérarchie de classes, leurs mécanismes et leur sécurité diffèrent considérablement.

static_cast : Transtypage basé sur la connaissance du programmeur

static_cast est utilisé pour des conversions qui sont considérées comme sûres par le programmeur, car elles sont basées sur des informations disponibles au moment de la compilation. Il est approprié pour des conversions entre types numériques (par exemple, int vers float, enum vers int), ainsi que pour des conversions de pointeurs entre types de classes apparentées, à condition que la relation soit connue au moment de la compilation.

Voici un exemple typique de l'utilisation de static_cast dans une hiérarchie de classes :


class Base {};
class Derived : public Base {};

void exempleStaticCast(Base* pointeurBase, Derived* pointeurDerived) {
    // Conversion d'un pointeur de base vers un pointeur dérivé.
    // Dangereux si pointeurBase ne pointe pas réellement vers un objet Derived.
    Derived* pointeurDerived2 = static_cast<derived>(pointeurBase);

    // Conversion d'un pointeur dérivé vers un pointeur de base.
    // Généralement sûr car un objet Derived contient toujours un objet Base.
    Base* pointeurBase2 = static_cast<base></base>(pointeurDerived);
}
</derived>

La première conversion (Base* vers Derived*) est potentiellement dangereuse. Si pointeurBase ne pointe pas vers une instance de Derived, l'utilisation de pointeurDerived2 peut entraîner un comportement indéfini, comme un crash dû à un accès mémoire hors limites, surtout si des méthodes ou des membres spécifiques à Derived sont appelés.

static_cast peut également être utilisé pour convertir des objets entre types définis par l'utilisateur, à condition qu'un constructeur approprié existe dans le type de destination. Par exemple, convertir un objet de type A en type B peut être réalisé si B a un constructeur qui accepte un objet A ou une référence vers A.


class A {};
class B {
public:
    B(const A& objetA) {
        // ... initialisation basée sur objetA ...
    }
};

void exempleConstructeurStaticCast() {
    A objetA;
    B objetB = static_cast<b>(objetA); // Appel implicite du constructeur B(const A&)
}
</b>

Un autre usage fréquent de static_cast concerne les types fondamentaux. Lors de la conversion d'un type plus grand vers un type plus petit (par exemple, int vers char), static_cast effectue une troncature simple, ignorant les bits de poids fort. De même, la conversion d'un entier vers une énumération peut produire une valeur non représentée dans l'énumération, conduisant à un état potentiellement invalide.


enum JourSemaine {
    Lundi = 1, Mardi, Mercredi, Jeudi, Vendredi, Samedi, Dimanche
};

void exempleEnumTruncation() {
    int valeurInvalide = 8;
    JourSemaine jourInconnu = static_cast<joursemaine>(valeurInvalide);
    // jourInconnu contiendra la valeur 8, bien qu'il n'y ait pas de "Jour 8".
}
</joursemaine>

Enfin, static_cast peut convertir n'importe quelle expression en type void, ce qui est utile pour ignorer explicitement une valeur de retour.

Il est crucial de noter que static_cast ne retire pas les qualificateurs const ou volatile. Pour cela, const_cast doit être utilisé.

dynamic_cast : Transtypage sûr avec vérification au runtime

dynamic_cast est conçu pour les conversions de pointeurs et de références au sein d'une hiérarchie de classes, mais il effectue une vérification de type au moment de l'exécution. Cela le rend beaucoup plus sûr que static_cast pour les conversions descendantes (de base vers dérivé).

Considérons la même hiérarchie de classes que précédemment, mais avec une méthode virtuelle pour activer le RTTI (Run-Time Type Information) :


class Base {
public:
    virtual ~Base() {} // Nécessaire pour dynamic_cast sur les pointeurs
    virtual void methodeVirtuelle() {}
};

class Derived : public Base {};

void exempleDynamicCast(Base* pointeurBase) {
    // Tentative de conversion de Base* vers Derived*.
    // Vérifie si pointeurBase pointe réellement vers un objet Derived.
    Derived* pointeurDerived = dynamic_cast<derived>(pointeurBase);

    if (pointeurDerived) {
        // La conversion a réussi. pointeurDerived est valide.
    } else {
        // La conversion a échoué. pointeurBase ne pointe pas vers un objet Derived.
        // pointeurDerived est nullptr.
    }
}
</derived>

Si pointeurBase pointe vers une instance de Derived (ou d'une classe dérivée de Derived), dynamic_cast renverra un pointeur valide vers cet objet. Si pointeurBase est nullptr, dynamic_cast renverra nullptr. Dans le cas où pointeurBase pointe vers un objet qui n'est pas une instance de Derived, dynamic_cast renverra nullptr. Ceci contraste fortement avec static_cast, qui, dans une situation similaire, renverrait un pointeur potentiellement invalide.

L'utilisation de dynamic_cast nécessite que la classe de base ait au moins une fonction membre virtuelle. Cela permet au compilateur d'inclure les inofrmations RTTI nécessaires pour effectuer la vérification au runtime.

Résumé des différences clés :

  • Sécurité : dynamic_cast est plus sûr pour les conversions descendantes car il vérifie le type au runtime. static_cast ne fait aucune vérificasion et repose sur la certitude du programmeur.
  • Moment de la vérification : static_cast vérifie la validité de la conversion à la compilation. dynamic_cast vérifie la validité au runtime.
  • Cas d'échec : dynamic_cast renvoie nullptr (pour les pointeurs) ou lève une exception std::bad_cast (pour les références) en cas d'échec. static_cast peut produire des résultats incorrects ou indéfinis sans signaler d'erreur explicite.
  • Performance : dynamic_cast a un léger surcoût en performance dû à la vérificasion au runtime, tandis que static_cast est généralement plus rapide car il n'effectue pas de telles vérifications.
  • Cas d'usage : Utilisez static_cast pour les conversions connues comme sûres (types numériques, conversions ascendantes de pointeurs, conversions implicites) et dynamic_cast lorsque vous devez interroger la nature d'un objet polymorphique au runtime avant de tenter une conversion descendante.

Étiquettes: C++ type casting static_cast dynamic_cast RTTI

Publié le 5 juin à 18h31