Chapitre 5 : opérateurs de transtypage et RTTI #
Slides #
Version imprimable (faire CTRL+P)
Exemples #
1242.2_05.01_DynamicCast
main.cpp
#include <print>
#include <typeinfo>
class Pet
{
public:
virtual void show() const { std::println("Pet"); }
virtual ~Pet() = default;
};
class Dog : public Pet
{
public:
void show() const override { std::println("Dog"); }
};
class Cat : public Pet
{
public:
void show() const override { std::println("Cat"); }
};
class Siamese : public Cat
{
public:
void show() const override { std::println("Siamese cat"); }
void siamFunction() {}
};
int main()
{
Pet *myPet = new Cat; // upcast: always safe
// %p: pointer as a void*
std::println("myPet = {:p}\n", static_cast<void *>(myPet));
// dynamic_cast returns nullptr when the runtime type does not match
auto myDog = dynamic_cast<Dog *>(myPet);
auto myCat = dynamic_cast<Cat *>(myPet);
std::println("myDog = {:p} (nullptr: cast failed)", static_cast<void *>(myDog));
std::println("myCat = {:p} (not nullptr: cast succeeded)\n", static_cast<void *>(myCat));
// C-style casts compile without error but bypass runtime type checks
auto myDog2 = (Dog *)myPet;
auto myCat2 = (Cat *)myPet;
std::println("myDog2 = {:p}", static_cast<void *>(myDog2));
std::println("myCat2 = {:p}\n", static_cast<void *>(myCat2));
auto mySiamese = dynamic_cast<Siamese *>(myPet); // fails: Cat is not a Siamese
std::println("mySiamese = {:p} (nullptr: cast failed)\n", static_cast<void *>(mySiamese));
Pet *myOtherPet = new Siamese;
auto mySiamese2 = dynamic_cast<Siamese *>(myOtherPet); // succeeds
std::println("mySiamese2 = {:p} (not nullptr: cast succeeded)\n", static_cast<void *>(mySiamese2));
myCat = dynamic_cast<Cat *>(myOtherPet); // succeeds: Siamese IS-A Cat
std::println("myCat = {:p}", static_cast<void *>(myCat));
std::println("Type of *myCat: {}", typeid(*myCat).name());
// Virtual dispatch still calls Siamese::show() even through a Cat*
myCat->show();
delete myPet;
delete myOtherPet;
return 0;
}1242.2_05.02_TypeidPolymorphic
main.cpp
#include <print>
#include <typeinfo>
class Animal
{
public:
virtual ~Animal() = default;
};
class Mammal : public Animal
{
};
class Dog : public Mammal
{
};
class Poodle : public Dog
{
};
int main()
{
Animal *ptr = new Poodle;
std::println("{}", typeid(bool).name());
// typeid on a pointer gives the declared pointer type
std::println("Type of ptr : {}", typeid(ptr).name());
// typeid on the dereferenced pointer gives the actual runtime type
// (only accurate for polymorphic classes — requires at least one virtual method)
std::println("Type of *ptr : {}", typeid(*ptr).name());
std::println("\nThe animal pointed to by ptr {} a Dog",
typeid(*ptr) == typeid(Dog) ? "is" : "is not");
std::println("The animal pointed to by ptr {} a Poodle",
typeid(*ptr) == typeid(Poodle) ? "is" : "is not");
std::println("\nThe animal pointed to by ptr is a {}", typeid(*ptr).name());
delete ptr;
return 0;
}1242.2_05.03_TypeidSimpleTypes
main.cpp
#include <print>
#include <typeinfo>
int main()
{
auto integer = 3;
auto real = 3.14f;
auto vDouble = 2.12;
auto character = 'a';
std::println("Type of integer : {}", typeid(integer).name());
std::println("Type of real : {}", typeid(real).name());
std::println("Type of vDouble : {}", typeid(vDouble).name());
std::println("Type of character : {}", typeid(character).name());
if (typeid(integer) == typeid(int))
{
std::println("integer is an int");
}
else
{
std::println("integer is not an int");
}
return 0;
}1242.2_05.04_DowncastingDanger
main.cpp
#include <print>
class A
{
public:
virtual void display() const { std::println("Class A"); }
void functionA() const { std::println("functionA"); }
virtual ~A() = default;
};
class B : public A
{
public:
void display() const override { std::println("Class B"); }
void functionB() const { std::println("functionB"); }
};
class C : public B
{
public:
void display() const override { std::println("Class C"); }
int c[100];
virtual void functionC()
{
std::println("functionC");
c[99] = 1;
std::println("c[99] = {}", c[99]);
}
};
int main()
{
A *objAB = new B;
std::println("objAB = {:p}", static_cast<void *>(objAB));
objAB->display();
objAB->functionA();
// Cannot call functionB() through A*, even though the object is a B
std::println("");
// dynamic_cast succeeds: objAB actually points to a B
B *objB = dynamic_cast<B *>(objAB);
std::println("objB = {:p}", static_cast<void *>(objB));
objB->display();
objB->functionA();
objB->functionB();
std::println("");
// dynamic_cast returns nullptr: objAB is B, not C
C *objC = dynamic_cast<C *>(objB);
std::println("objC = {:p} (null: objB is not a C)\n", static_cast<void *>(objC));
// C-style cast bypasses the runtime check — objC2 points to a B object
// but the compiler treats it as C*. Accessing C-specific data is undefined behavior.
C *objC2 = (C *)objB;
std::println("objC2 = {:p}", static_cast<void *>(objC2));
objC2->display();
objC2->functionA();
objC2->functionB();
objC2->c[50] = 1;
// Calling a virtual function through an invalid cast crashes at runtime
// objC2->functionC(); // uncomment to observe crash
C *objC3 = new C;
std::println("\nobjC3 = {:p}", static_cast<void *>(objC3));
for (int i = 0; i < 100; i++)
{
objC3->c[i] = 3;
}
// objC2->c[50] was 1 but the value depends on where objC3 was allocated
std::println("objC2->c[50] = {}", objC2->c[50]);
std::println("objC3->c[0] = {}", objC3->c[0]);
delete objAB;
delete objC3;
return 0;
}1242.2_05.05_TypeidVsDynamicCast
main.cpp
#include <print>
#include <typeinfo>
class Animal
{
public:
virtual ~Animal() = default;
};
class Mammal : public Animal
{
};
class Dog : public Mammal
{
};
class Poodle : public Dog
{
};
int main()
{
Animal *ptr = new Poodle;
// typeid ignores the inheritance hierarchy: Poodle is NOT Dog (exactly)
std::println("typeid == Dog : {}", typeid(*ptr) == typeid(Dog)); // false
std::println("typeid == Poodle : {}", typeid(*ptr) == typeid(Poodle)); // true
// dynamic_cast respects inheritance: Poodle IS-A Dog IS-A Mammal IS-A Animal
std::println("dynamic_cast<Dog*> : {}", dynamic_cast<Dog *>(ptr) != nullptr); // true
std::println("dynamic_cast<Poodle*> : {}", dynamic_cast<Poodle *>(ptr) != nullptr); // true
std::println("dynamic_cast<Mammal*> : {}", dynamic_cast<Mammal *>(ptr) != nullptr); // true
delete ptr;
return 0;
}1242.2_05.06_BasicCasts
main.cpp
#include <print>
int main()
{
// static_cast
{
int i = 100;
[[maybe_unused]]
auto c = static_cast<char>(i); // int -> char
auto f = 100.0f;
i = static_cast<int>(f); // float -> int
class Base
{
};
class Deri : public Base
{
};
[[maybe_unused]]
auto d = new Deri;
[[maybe_unused]]
auto b = static_cast<Base *>(d); // Deri* -> Base*
}
// reinterpret_cast
{
auto f = 99.0f;
auto pf = &f;
auto d = 99.0;
auto pd = &d;
// Wont compile
// pd = static_cast<double *>(pf);
pd = reinterpret_cast<double *>(pf);
int i = 99;
// UB but compiles
[[maybe_unused]]
auto ptr = reinterpret_cast<char *>(i); // int -> char*
[[maybe_unused]]
auto ref = reinterpret_cast<char &>(i); // int -> char&
[[maybe_unused]]
auto pf2 = reinterpret_cast<float *>(pd); // double* -> float*
}
// const_cast
{
class A
{
};
[[maybe_unused]]
const A *cptr = new A;
[[maybe_unused]]
auto ptr = const_cast<A *>(cptr); // const A* -> A*
A a;
[[maybe_unused]]
const A &cref = a;
[[maybe_unused]]
auto ref = const_cast<A &>(cref); // const A& -> A&
}
return 0;
}Récapitulatif #
| Opérateur | Vérification | Risque | Cas d’usage typique |
|---|---|---|---|
static_cast<T> | 🔵 Le compilateur valide la compatibilité des types | ⚠️ Downcast incorrect (UB) | Conversions numériques, upcast/downcast certain, void* ↔ T* |
reinterpret_cast<T> | 🔴 Réinterprétation brute des bits | ❌ Très élevé (UB dans la plupart des cas) | Inspection mémoire brute (T → char*), interfaçage bas niveau. Préférer std::bit_cast (C++20). |
const_cast<T> | 🔵 Seul cast qui modifie les qualificateurs | ⚠️ Modifier un objet réellement const → UB | Voir ci-dessous |
dynamic_cast<T> | 🟢 RTTI via la vtable | ✅ Retourne nullptr ou lève bad_cast | Downcast incertain dans une hiérarchie polymorphique. Nécessite au moins une méthode virtuelle. |
Vérifications : #
🔵 Compilation 🟢 Exécution 🟡 Modéré 🔴 Aucune
Risque : #
✅ : sûr ⚠️ : modéré ❌ : élevé
Exemple utile avec const_cast
#
Factoriser le code entre la version const et non-const d’une méthode :
- Ajouter
constà*thispour sélectionner la surchargeconst - Appeler la méthode
constqui contient la vraie logique - Retirer le
constdu résultat retourné
const int& MyVec::operator[](int i) const { return data[i]; }
int& MyVec::operator[](int i)
{
const MyVec &self = *this;
const int &result = self[i];
return const_cast<int&>(result);
}const.Serie 5.1 #
Exercice 1 : RTTI #
On aimerait pouvoir comparer des objets afin de savoir s’ils sont de la même classe et si leur contenu est identique.
- Reprendre le projet de la série 4.2 concernant les figures.
- Implémentez la méthode suivante
bool compareShapes(Figure *fig1, Figure *fig2)qui retoune vrai si :- les objets sont des instances de la même classe (utiliser typeid)
- le point
posdéfinit dansFigureest le même pour les 2 objets.
- Le
main()a l’aspect suivant :
int main()
{
std::println("========== Exercise 1 : typeid ==========");
Rectangle r1(Point(1, 2), 4.0, 10.0);
Rectangle r2(Point(1, 2), 4.0, 10.0);
Rectangle r3(Point(10, 20), 10.0, 20.0);
Circle c1(Point(1.1, 5.3), 5.0);
std::println("\n### r1 and r2");
std::println("same: {}", compareShapes(&r1, &r2));
std::println("\n### r1 and r3");
std::println("same: {}", compareShapes(&r1, &r3));
std::println("\n### r1 and c1");
std::println("same: {}", compareShapes(&r1, &c1));
std::println("\n========== Exercise 2 : dynamic_cast ==========");
Figure *myShapes[3];
myShapes[0] = new Circle(Point(1.1, 5.3), 5.0);
myShapes[1] = new Triangle(Point(2, 2), Point(10, 3), Point(-1, -1));
myShapes[2] = new Rectangle(Point(4, 2), 4.0, 10.0);
std::println("\n-- Shapes with radius when applicable --");
for (auto shapePtr : myShapes)
{
shapePtr->show();
}
// Use auto & to modify the pointers in the array
for (auto &shape : myShapes)
{
delete shape;
shape = nullptr;
}
return 0;
}Le résultat :
========== Exercise 1 : typeid ==========
### r1 and r2
-- Comparing 9Rectangle with 9Rectangle
typeid match
position: same
same: true
### r1 and r3
-- Comparing 9Rectangle with 9Rectangle
typeid match
position: different
same: false
### r1 and c1
-- Comparing 9Rectangle with 6Circle
typeid differ
same: false
========== Exercise 2 : dynamic_cast ==========
-- Shapes with radius when applicable --
Circle: Point : (1.1, 5.3), radius=5
>>>> radius: 5
Triangle: Point : (2, 2), Point : (10, 3), Point : (-1, -1)
Rectangle: Point : (4, 2), w=10, h=4Exercice 2 : transtypage dynamique #
On aimerait pouvoir accéder à une méthode spécifique à une des classes dérivées, sans connaitre le pointeur sur cette classe, mais uniquement le pointeur sur la classe mère.
- reprendre le projet de la série 4.2 concernant les figures.
- ajouter la méthode
getRadius()à la classeCircle - le code ci-dessous ne fonctionne pas car la méthode n’existe pas dans les classes
Figure,RectangleetTriangle.
int main()
{
Figure *myShapes[3];
myShapes[0] = new Circle(Point(1.1, 5.3), 5.0);
myShapes[1] = new Triangle(Point(2, 2), Point(10, 3), Point(-1, -1));
myShapes[2] = new Rectangle(Point(4, 2), 4.0, 10.0);
cout << "------- List of shapes -----------" << endl;
for (auto shape : myShapes)
{
shape->show();
//shape->getRadius();
}
return 0;
}Améliorer ce code afin qu’il offre la sortie suivante :
Cercle:
Point : (1.1, 5.3), radius=5
>>>>>>>>>>>>>>> The radius is: 5 (obtenu au moyen de getRadius()
Triangle:
Point : (2, 2)
Point : (10, 3)
Point : (-1, -1)
Rectangle:
Point : (4, 2), l = 10, h = 4Serie 5.2 #
Exercice 1 #
Soit un tableau de pointeurs sur des Figure (cf série 4.2), on aimerait faire une copie de ce tableau.
Pour cela, il faut réallouer la mémoire pour chacun des objets pointés.
int main()
{
Figure *myShapes[3];
myShapes[0] = new Circle(Point(1.1, 5.3), 5.0);
myShapes[1] = new Triangle(Point(2, 2), Point(10, 3), Point(-1, -1));
myShapes[2] = new Rectangle(Point(4, 2), 4.0, 10.0);
Figure *myShapesCopy[3];
return 0;
}- reprendre le projet de la série 4.2 concernant les
Figure - utiliser le transtypage dynamique.
Afin de vérifier le bon fonctionnement, désallouer d’abord les Figure du tableau original puis affichez la copie.
Exercice 2 #
Reprendre le problème précédent, en tirant bénéfice du polymorphime.
Dans la classe Figure, déclarer une méthode virtuelle pure clone().
Chaque classe dérivée l’implémente de manière :
- à se cloner elle-même
- à allouer dynamiquement de la mémoire
- à retourner un pointeur.
Afin de vérifier le bon fonctionnement, désallouer d’abord les Figure du tableau original puis affichez la copie.