FAQ C++Consultez toutes les FAQ
Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021 Ajouter une question
Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.
Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.
Sur ce, nous vous souhaitons une bonne lecture.
Contrairement au C, les cast C++ sont effectués au moyen d'opérateurs spécifiques (mots-clés réservés). Le choix de ces opérateurs dépend du type de cast, car le C++ différencie quatre types de conversion explicite. Tout d'abord, trois opérateurs sont dédiés aux conversions statiques (effectuées à la compilation) :
- static_cast : entre types de même famille. Il s'agit la plupart du temps d'expliciter des conversions qui auraient pu être effectuées de manière implicite, mais souvent avec un avertissement du compilateur.
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10
11
12
13
14// conversion de int en char int i = 100; char c = static_cast<char>( i ); // conversion de float en int float f = 100.0f; i = static_cast<int>( f ); // conversion classes dérivée -> classe parent class A {}; class B : public A {}; B *b = new B; A *a = static_cast<A*>( b );
- reinterpret_cast : entre types de familles différentes. Il s'agit simplement de dire au compilateur « je sais que je manipule une donnée de type X, mais on va faire comme si elle était de type Y ». De ce fait aucune donnée n'est physiquement modifiée, cet opérateur de conversion n'est qu'une indication pour le compilateur.
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9// Conversion de int en pointeur de char int i; char * ptr = reinterpret_cast<char*>( i ); // Conversion de int en référence sur char char & ref = reinterpret_cast<char&>( i ); // Conversion pointeur de char -> pointeur de double double * ptr2 = reinterpret_cast<double*>( ptr );
- const_cast: entre un type donné constant et le même type avec / sans les qualificateurs const ou volatile. Cet opérateur est rarement utilisé, car :
- La conversion non-const -> const est implicite.
- La conversion const -> non-const relève en fait bien souvent d'une erreur de conception.
- Le qualificateur volatile est rarement utilisé.
Code c++ : Sélectionner tout 1
2
3
4
5
6
7
8
9
10class A {}; // Conversion de const A * en A * const A * cptr; A * ptr = const_cast<A*>( cptr ); // Conversion de const A & en A & A a; const A & cref = a; A & ref = const_cast<A&>( cref );
Le C++ supporte aussi un autre type de conversion : depuis une classe parent vers une classe enfant (downcasting) ou depuis une classe parent vers une autre classe parent (crosscasting). La classe parent doit être à usage polymorphe, autrement dit elle doit obligatoirement comporter au moins une fonction membre virtuelle, et être manipulée au moyen d'un pointeur ou d'une référence (la virtualité implique l'utilisation de pointeurs ou de références, voir Que signifie le mot-clé virtual ?). Ce type de cast étant dynamique (effectué à l'exécution), il est susceptible d'échouer et de lever une exception std::bad_cast dans le cas d'une conversion de références, ou de renvoyer NULL dans le cas d'une conversion de pointeurs.
L'opérateur associé à ce cast est dynamic_cast :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #include <iostream> #include <string> #include <typeinfo> class A { public: virtual std::string get_type() = 0; }; class B : public A { public: virtual std::string get_type() { return "B"; } }; class C : public A { public: virtual std::string get_type() { return "C"; }; }; A * create_B_or_C() { static int nb = 0; // si nb est pair, on crée un B, sinon un C ++nb; if ( nb % 2 == 0 ) { return new B; } return new C; } int main() { for ( int i = 0; i < 5; ++i ) { A *a = create_B_or_C(); std::cout << "Test sur un " << a->get_type() << " : "; B *b = dynamic_cast<B*>( a ); if ( b == 0 ) { // échec du cast en B, il doit s'agir d'un C // lève std::bad_cast s'il ne s'agit pas d'un C try { C & c = dynamic_cast<C&>( *a ); std::cout << "il s'agit d'un C.\n"; } catch ( const std::bad_cast & ) { std::cout << "Oups!\n"; } } else { std::cout << "il s'agit d'un B.\n"; } delete a; } } |
Dernière remarque concernant dynamic_cast : celui-ci est souvent utilisé à tort, surtout par les débutants. Voir Pourquoi l'utilisation du downcasting est-il souvent une pratique à éviter ?. Voir également Qu'est-ce que le cross-casting ?.
Si vous souhaitez tester votre maîtrise des casts C++, nous vous renvoyons à l'item n° 17 des GOTW (Guru Of The Week)
( http://www.gotw.ca/gotw/017.htm).
On est souvent tenté d'utiliser l'opérateur de conversion dynamic_cast pour effectuer un downcasting (voir Comment effectuer une conversion de type explicite (cast) ?), mais il s'agit fréquemment d'une erreur. Pourquoi ? Car conceptuellement parlant, on a en réalité rarement besoin de connaître le type réel d'un objet que l'on manipule via sa classe de base. Si l'on se retrouve dans une telle situation, c'est probablement que :
- La fonction que l'on écrit ne travaille en fait pas sur la classe de base, mais seulement sur certaines classes dérivées bien identifiées.
- On n'a pas exploité un éventuel polymorphisme dynamique (utilisation des fonctions virtuelles, voir Que signifie le mot-clé virtual ?).
- On n'a pas exploité un éventuel polymorphisme statique (utilisation des templates et des surcharges).
- On a mal conçu notre hiérarchie.
Le downcasting est donc à utiliser avec parcimonie, lorsque l'on n'a pas le choix ou que l'on sait exactement ce que l'on fait. Par exemple dans le cas des plugin, ou encore lorsque l'on travaille avec un code, une bibliothèque ou une API qui ne connaîtrait (et donc ne pourrait manipuler) que la classe de base d'une hiérarchie. Les classes dérivées étant écrites par le client, le downcasting est donc une solution simple pour communiquer avec la bibliothèque en question.
On peut également citer des implémentations de double-dispatching utilisant le downcasting (notamment dans la bibliothèque Loki).
Il faut donc toujours s'interroger sur l'utilisation que l'on fait de dynamic_cast : est-ce une nécessité, ou seulement un moyen pratique de contourner une erreur de conception ?
Le cross-casting est une possibilité offerte par le C++ grâce à son support de l'héritage multiple. Dans l'exemple suivant :
Code c++ : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // A B // \ / // C class A { public: virtual ~A(); }; class B { public: virtual ~B(); }; class C : public A, public B { }; |
Code c++ : | Sélectionner tout |
1 2 | A *a = new C; B *b = dynamic_cast<B*>( a ); |
Code c++ : | Sélectionner tout |
1 2 3 | A *a = new C; C *c = dynamic_cast<C*>( a ); // downcasting de A en C B *b = static_cast<B*>( c ); // cast de C en B |
Pour plus de précisions sur le cross-casting et ses applications, vous pouvez lire ce document : Cross Casting: The Capsule Pattern.
Proposer une nouvelle réponse sur la FAQ
Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour çaLes sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.