• Aucun résultat trouvé

Le transtypage est puissant, mais il peut causer des maux de tête parce que dans certaine situations il peut forcer le compilateur à traiter les données comme si elles étaient (par exemple) plus larges qu'elles ne le sont en réalité, donc cela peut occuper plus d'espace en mémoire ; et peut écraser d'autres données. Cela arrive habituellement quand un pointeur est transtypé, et non quand un simple transtypage est fait comme celui montré plus tôt.

C++ a une syntaxe de transtypage supplémentaire, qui suit la syntaxe d'appel de fonction. Cette syntaxe met des parenthèses autour de l'argument, comme un appel de fonction, plutôt qu'autour du type de la donnée :

//: C03:FunctionCallCast.cpp int main() {

float a = float(200);

// Ceci est équivalent à:

float b = (float)200;

} ///:~

Bien sûr dans le cas précédent vous ne pouvez pas réellement avoir besoin de transtypage; vous pouvez juste dire 200 .f ou 200.0f (en effet, c'est ce que le compilateur fera normalement pour l'expression précédente). Le transtypage est habituellementutilisé avec des variables, plutôt qu' avecles constantes.

3.7.12 - Transtypage C++ explicite

Le transtypage doit être utilisé avec précaution, parce que ce que vous faites est de dire au compilateur “oublie le contrôle des types – traite le comme cet autre type à la place.” C'est à dire, vous introduisez une faille dans le système de types du C++ et empechez le compilateur de vous dire que vous êtes en train de faire quelque chose de mal avec ce type. Ce qui est pire, le compilateur vous croit implicitement et ne peut exécuter aucun autre contrôle pour détecter les erreurs. Une fois que vous commencez à transtyper, vous vous ouvrez à toutes sortes de problèmes. En fait, tout programme qui utilise beaucoup de transtypages doit être abordé avec suspicion, peut importe le nombre de fois où on vous dit que ça “doit” être fait ainsi.En général, les transtypages devraient être peu nombreux et réduits au traitement de problèmes spécifiques.

Une fois que vous avez compris cela et que vous êtes en leur présence dans un programme bogué, votre premier réflexe peut être de regarder les transtypages comme pouvant être les coupables. Mais comment localiser les transtypages du style C ? Ils ont simplement un nom de type entre parenthèses, et si vous commencez à chercher ces choses vous découvrirez que c'est souvent difficile de les distinguer du reste du code.

Le standard C++ inclut une syntaxe de transtypage explicite qui peut être utilisée pour remplacer complètement l'ancien style C de transtypage (bien sûr, les transtypages de style C ne peuvent pas être declarés hors la loi sans briser la compatibilité avec du code existant, mais les compilateurs peuvent facilement vous signaler un transtypage de l'ancien style). La syntaxe de transtypage explicite est ainsi faite que vous pouvez facilement la trouver, comme vous pouvez la voir par son nom :

Le trois premiers transtypages explicites seront décrits dans la prochaine section, alors que le dernier sera expliqué seulement après que vous en ayez appris plus, dans le chapitre 15.

static_cast

Un static_castest utilisé pour toutes les conversions qui sont bien définies. Ceci inclut les conversions “sûres” que le compilateur peut vous autoriser de faire sans un transtypage et les conversions moins sûres qui sont néanmoins bien définies. Les types de conversions couverts par static_castincluent typiquement les conversions de type sans danger (implicites), les conversions limitantes (pertes d'information), le forçage d'une conversion d'un void*, conversions implicite du type, et la navigation statique dans la hiérarchie des classes (comme vous n'avez pas vu les classes et l'héritage actuellement, ce dernier est repoussé au chapitre 15):

//: C03:static_cast.cpp

// (1) Conversion typique sans transtypage:

l = i; i = f; // Peut perdre des informations // Dis #Je sais,# elimine les avertissements:

i = static_cast<int>(l);

i = static_cast<int>(f);

char c = static_cast<char>(i);

// (3) Forcer une conversion depuis un void* : void* vp = &i;

// Ancienne forme: produit une conversion dangereuse:

float* fp = (float*)vp;

// La nouvelle façon est également dangereuse:

fp = static_cast<float*>(vp);

// (4) Conversion de type implicite, normalement // exécutée par le compilateur:

double d = 0.0;

int x = d; // Conversion de type automatique x = static_cast<int>(d); // Plus explicite func(d); // Conversion de type automatique func(static_cast<int>(d)); // Plus explicite } ///:~

Dans la section (1), vous pouvez voir le genre de conversion que vous utilisiez en C, avec ou sans transtypage.

Promouvoir un inten un longou floatn'est pas un problème parce que ces derniers peuvent toujours contenir que qu'un intpeut contenir. Bien que ce ne soit pas nécessaire, vous pouvez utiliser static_castpour mettre en valeur

cette promotion.

La conversion inverse est montrée dans (2). Ici, vous pouvez perdre des données parce que un intn'est pas “large”

comme un longou un float; ce ne sont pas des nombres de même taille. Ainsi ces convertions sont appelées conversions limitantes. Le compilateur peut toujours l'effectuer, mais peut aussi vous retourner un avertissement.

Vous pouvez éliminer le warning et indiquer que vous voulez vraiment utiliser un transtypage.

L'affectation à partir d'un void*n'est pas permise sans un transtypage en C++ (à la différence du C), comme vu dans (3). C'est dangereux et ça requiert que le programmeur sache ce qu'il fait. Le static_cast, est plus facile à localiser que l'ancien standard de transtypage quand vous chassez les bugs.

La section (4) du programme montre le genre de conversions implicites qui sont normalement effectuées automatiquement par le compilateur. Celles-ci sont automatiques et ne requièrent aucun transtypage, mais à nuoveau un static_castmet en évidence l'action dans le cas où vous voudriez le faire apparaitre clairement ou le reperer plus tard.

const_cast

Si vous voulez convertir d'un consten un non constou d'un volatileen un non volatile, vous utilisez const_cast.

C'est la seuleconversion autorisée avec const_cast; si une autre conversion est impliquée, il faut utiliser une expression séparée ou vous aurez une erreur de compilation.

//: C03:const_cast.cpp int main() {

const int i = 0;

int* j = (int*)&i; // Obsolete

j = const_cast<int*>(&i); // A privilegier

// Ne peut faire simultanément de transtypage additionnel:

//! long* l = const_cast<long*>(&i); // Erreur volatile int k = 0;

int* u = const_cast<int*>(&k);

} ///:~

Si vous prenez l'adresse d'un objet const, vous produisez un pointeur sur un const, et il ne peut être assigné à un pointeur non constsans un transtypage. L'ancien style de transtypage peut l'accomplir, mais le const_castest approprié pour cela. Ceci est vrai aussi pour un volatile.

reinterpret_cast

Ceci est le moins sûr des mécanismes de transtypage, et le plus apprecié pour faire des bugs. Un reinterpret_castprétend qu'un objet est juste un ensemble de bit qui peut être traité (pour quelques obscures raisons) comme si c'était un objet d'un type entièrement différent. C'est le genre de bricolage de bas niveau qui a fait mauvaise réputation au C. Vous pouvez toujours virtuellement avoir besoin d'un reinterpret_castpour retourner dans le type original de la donnée(ou autrement traiter la variable comme son type original) avant de faire quoi que ce soit avec elle.

//: C03:reinterpret_cast.cpp

#include <iostream>

using namespace std;

const int sz = 100;

struct X { int a[sz]; };

void print(X* x) {

for(int i = 0; i < sz; i++) cout << x->a[i] << ' ';

cout << endl << "---" << endl;

}

int main() { X x;

print(&x);

int* xp = reinterpret_cast<int*>(&x);

for(int* i = xp; i < xp + sz; i++)

*i = 0;

// Ne pas utiliser xp comme un X* à ce point

// à moins de le retranstyper dans son état d'origine:

print(reinterpret_cast<X*>(xp));

// Dans cette exemple, vous pouvez aussi juste utiliser // l'identifiant original:

print(&x);

} ///:~

Dans cet exemple simple, struct Xcontiens seulement un tableau de int, mais quand vous en créez un sur la pile comme dans X x, la valeur de chacun des ints est n'importe quoi (ceci est montré en utilisant la fonction print( )pour afficher le contenu de la struct). Pour les initialiser , l'adresse de Xest prise et transtypée en un pointeur de type int, le tableau est alors parcouru pour mettre chaque intà zéro. Notez comment la limite haute pour iest calculée par “l'addition” de szavec xp; le compilateur sait que vous voulez actuellement szpositions au départ de xpet utilise l'arithmétique de pointeur pour vous.

L'idée du reinterpret_castest que quand vous l'utilisez, ce que vous obtenez est à ce point différent du type original que vous ne pouvez l'utiliser comme tel à moins de le transtyper à nouveau. Ici, nous voyons le transtypage précédent pour un X*dans l'appel de print, mais bien sûr dès le début vous avez l'identifiant original que vous pouvez toujours utiliser comme tel. Mais le xpest seulement utile comme un int*, qui est vraiment une

“réinterpretation” du Xoriginal.

Un reinterpret_castpeut aussi indiquer une imprudence et/ou un programme non portable, mais est disponible quand vous décidez que vous devez l'utiliser.