Les quatre types de cast
En C++, on utilise plus un cast entre parenthèse, on utilise static_cast
, dynamic_cast
, const_cast
ou reinterpret_cast
.
static_cast
static_cast
peut être utilisé pour caster les types primitifs entre eux.
double d = 0.1; // Perte explicite de précision float f = static_cast<float>(d);
static_cast
est aussi utilisé pour caster une classe parent vers un type enfant.
struct B { int f() { return 2; } }; struct C : public B { int f() { return 3; } }; int main() { B b; C *c = static_cast<C *>(&b); return c->f(); }
dynamic_cast
dynamic_cast
permet de vérifier que le static_cast
est possible. Il est nécessaire que la classe soit polymorphique (au moins une méthode virtuelle).
struct B { virtual ~B() = default; int f() { return 2; } }; struct C : public B { int f() { return 3; } }; struct D : public B { int f() { return 3; } }; int main() { C c; B *b = &c; // d != nullptr D *d = static_cast<D *>(b); // d == nullptr d = dynamic_cast<D *>(b); return d == nullptr; }
Il est aussi possible d'utiliser dynamic_cast
avec des références. En cas d'échec, une exception std::bad_cast
sera généré.
#include <typeinfo> class A { public: virtual ~A() = default; }; class B : public A {}; class C {}; int main() { B b; A& a = dynamic_cast<A&>(b); try { C& c = dynamic_cast<C&>(b); } catch (const std::bad_cast&) { return 1; } return 0; }
Dynamic Casts with References Archive du 14/04/2021 le 23/06/2023
const_cast
const_cast
sert uniquement à enlever un const
à un type.
const char *t = "coucou"; void f(char *tt) { // Interdit tt[0] = 1; } int main() { f(const_cast<char *>(t)); return 0; }
reinterpret_cast
reinterpret_cast
est utilisé lorsqu'on souhaite caster un pointeur vers un autre sans rapport implicite. Son utilisation est souvent synonyme de mauvaises pratiques de codage.
int main() { int b[5]; short *s = reinterpret_cast<short*>(&b[0]); }
Les problèmes
Cast vers un parent d'un héritage multiple
Depuis Parent1
, caster this
vers Parent2
.
Il ne faut surtout pas faire (ci-dessous). Sinon, les méthodes virtuelles (au minimum) appelleront n'importe quoi. De toute façon d'une manière générale, reinterpret_cast
ne s'utilise que vers la classe la plus basse dans l'héritage.
Parent2* p = reinterpret_cast<Parent2*>(this);
Il faut faire (ci-dessous). Puis un cast
naturel se fera de Enfant
vers Parent2
.
Parent2* p = static_cast<Enfant*>(this);
reinterpret_cast sur une classe avec héritage
Un static_cast
ou un dynamic_cast
ne pose pas de problème dans le cas d'héritage multiple.
Par contre, un reinterpret_cast
d'un void *
doit toujours se faire sur la classe la plus basse (la plus enfant). Un void *
ne possède aucune information du type de la classe et donc le compilateur ne sait pas comment s'en sortir. Par exemple avec les méthodes virtuelles, il ne peut pas savoir à quelle classe appartient la première méthode en tête de la vtable
. Il y a les mêmes problèmes avec les attributs de la classe.
multiple inheritance: unexpected result after cast from void * to 2nd base class Archive du 04/03/2010 le 19/12/2019
Les détecters avec sanitizer
Les sanitizer peuvent détecter les erreurs de static_cast
/ reinterpret_cast
. Il est quand même nécessaire que la classe castée soit polymorphique.
struct A { virtual int f() { return 1; } }; struct B { virtual int f() { return 2; } }; struct C : public B {}; int main() { A a; B *b = reinterpret_cast<B *>(&a); return b->f(); }
Les classes A et B étant identiques, il est normal que le programme s'exécute correctement.
Mais avec un sanitizer clang++ -fsanitize=undefined -fno-sanitize-recover=all main.cc -o a.out -flto -fvisibility=hidden
:
main.cc:17:13: runtime error: member call on address 0x7ffc03ba2878 which does not point to an object of type 'B' 0x7ffc03ba2878: note: object is of type 'A' fc 7f 00 00 88 4d bf 29 2b 56 00 00 78 28 ba 03 fc 7f 00 00 00 09 cf 0b 25 bc 5d 17 00 00 00 00