FAQ sur la conversion de type C ++

Où puis-je trouver un article parfaitement compréhensible sur la conversion de type C ++ couvrant tous ses types (promotion, implicite / explicite, etc.)?

J’ai appris le C ++ depuis un certain temps et, par exemple, le mécanisme des fonctions virtuelles me semble plus clair que ce sujet. Mon opinion est que cela est dû aux auteurs du manuel qui compliquent trop (voir le livre de Stroustroup, etc.).

(Des compliments pour Crazy Eddie pour une première réponse, mais je pense que cela peut être clarifié)

Conversion de type

Pourquoi ça se passe?

La conversion de type peut se produire pour deux raisons principales. La première est que vous avez écrit une expression explicite, telle que static_cast(3.5) . Une autre raison est que vous avez utilisé une expression à un endroit où le compilateur avait besoin d’un autre type afin d’insérer la conversion. Par exemple, 2.5 + 1 entraînera une conversion implicite de 1 (un entier) à 1,0 (un double).

Les formes explicites

Il n’y a qu’un nombre limité de formes explicites. Premièrement, C ++ a 4 versions nommées: static_cast , dynamic_cast , const_cast et const_cast . C ++ prend également en charge l’ (Type) Expression C. Enfin, il existe un type de conversion “style constructeur” Type(Expression) .

Les 4 formes nommées sont documentées dans tout bon texte d’introduction. La static_cast style C se static_cast , const_cast ou reinterpret_cast , et la const_cast “constructor-style” est un raccourci pour un static_cast . Cependant, en raison de problèmes d’parsing, la conversion “constructor-style” requirejs un identifiant unique pour le nom du type; unsigned int(-5) ou const float(5) ne sont pas légaux.

Les formes implicites

Il est beaucoup plus difficile d’énumérer tous les contextes dans lesquels une conversion implicite peut avoir lieu. Comme C ++ est un langage OO typé, il existe de nombreuses situations dans lesquelles vous avez un object A dans un contexte où vous auriez besoin d’un type B. Les exemples sont les opérateurs intégrés, l’appel d’une fonction ou la capture d’une exception par valeur.

La séquence de conversion

Dans tous les cas, implicites et explicites, le compilateur essaiera de trouver une séquence de conversion . Une séquence de conversion est une série d’étapes qui vous permettent de passer du type A au type B. La séquence de conversion exacte choisie par le compilateur dépend du type de conversion. dynamic_cast est utilisé pour effectuer une conversion contrôlée de base en dérivé. Les étapes consistent donc à vérifier si Derived hérite de Base, via quelle classe intermédiaire. const_cast peut supprimer à la fois const et volatile . Dans le cas d’une static_cast , les étapes possibles sont les plus complexes. Il fera la conversion entre les types arithmétiques intégrés; il convertira les pointeurs de base en pointeurs dérivés et inversement, il considérera les constructeurs de classe (du type de destination) et les opérateurs de conversion de classe (du type de source), et appenda const et volatile . Il est évident que bon nombre de ces étapes sont orthogonales: un type arithmétique n’est jamais un type de pointeur ou de classe. En outre, le compilateur utilisera chaque étape au plus une fois.

Comme nous l’avons noté précédemment, certaines conversions de types sont explicites et d’autres sont implicites. Ceci est important pour static_cast car il utilise des fonctions définies par l’utilisateur dans la séquence de conversion. Certaines des étapes de conversion considérées par le compilateur peuvent être marquées comme explicit (En C ++ 03, seuls les constructeurs peuvent). Le compilateur ignorera (aucune erreur) toute fonction de conversion explicit pour les séquences de conversion implicites. Bien sûr, s’il ne rest aucune alternative, le compilateur donnera toujours une erreur.

Les conversions arithmétiques

Les types entiers tels que char et short peuvent être convertis en types “supérieurs” tels que int et long , et les types à virgule flottante plus petits peuvent également être convertis en types plus grands. Les types entiers signés et non signés peuvent être convertis l’un en l’autre. Les types entier et à virgule flottante peuvent être changés l’un pour l’autre.

Conversions de base et dérivées

Comme C ++ est un langage OO, il existe un certain nombre de transtypes dans lesquels la relation entre Base et Dérivé est importante. Ici, il est très important de comprendre la différence entre les objects réels, les pointeurs et les références (surtout si vous venez de .Net ou de Java). Tout d’abord, les objects réels. Ils ont précisément un type et vous pouvez les convertir en n’importe quel type de base (en ignorant les classes de base privées pour le moment). La conversion crée un nouvel object de type base. Nous appelons cela “trancher”; les parties dérivées sont tranchées.

Un autre type de conversion existe lorsque vous avez des pointeurs sur des objects. Vous pouvez toujours convertir un Derived* en une Base* , car chaque object dérivé contient un sous-object de base. C ++ appliquera automatiquement le décalage correct de Base avec Dérivé à votre pointeur. Cette conversion vous donnera un nouveau pointeur, mais pas un nouvel object. Le nouveau pointeur pointe vers le sous-object existant. Par conséquent, le casting ne coupera jamais la partie dérivée de votre object.

La conversion dans l’autre sens est plus délicate. En général, toutes les Base* ne désigneront pas un sous-object de base à l’intérieur d’un object dérivé. Les objects de base peuvent également exister ailleurs. Par conséquent, il est possible que la conversion échoue. C ++ vous donne deux options ici. Soit vous indiquez au compilateur que vous êtes certain de pointer un sous-object à l’intérieur d’un object dérivé via un static_cast(baseptr) , ou vous demandez au compilateur de vérifier avec dynamic_cast(baseptr) . Dans ce dernier cas, le résultat sera nullptr si baseptr ne baseptr pas réellement vers un object dérivé.

Pour les références à Base et Derived, il en va de même sauf pour dynamic_cast(baseref) : il lancera std::bad_cast au lieu de renvoyer un pointeur null. (Il n’existe pas de références nulles).

Conversions définies par l’utilisateur

Il existe deux manières de définir les conversions utilisateur: via le type de source et via le type de destination. La première consiste à définir un operator DestinatonType() const membre operator DestinatonType() const dans le type source. Notez qu’il n’a pas de type de retour explicite (c’est toujours DestinatonType ) et qu’il est const . Les conversions ne doivent jamais changer l’object source. Une classe peut définir plusieurs types en lesquels elle peut être convertie, simplement en ajoutant plusieurs opérateurs.

Le second type de conversion, via le type de destination, repose sur des constructeurs définis par l’utilisateur. Un constructeur T::T qui peut être appelé avec un argument de type U peut être utilisé pour convertir un object U en un object T. Peu importe que ce constructeur ait des arguments par défaut supplémentaires, ni que l’argument U soit passé par valeur ou par référence. Cependant, comme noté précédemment, si T::T(U) est explicit , il ne sera pas pris en compte dans les séquences de conversion implicites.

il est possible que plusieurs séquences de conversion entre deux types soient possibles, à la suite de séquences de conversion définies par l’utilisateur. S’agissant essentiellement d’appels de fonction (à des opérateurs ou à des constructeurs définis par l’utilisateur), la séquence de conversion est choisie via la résolution de surcharge des différents appels de fonction.

Je n’en connais pas, alors voyons si cela ne peut pas être fait ici … j’espère que je comprends bien.

Tout d’abord, implicite / explicite:

La “conversion” explicite se produit partout où vous faites un casting. Plus spécifiquement, un static_cast. D’autres conversions échouent dans la conversion ou couvrent une gamme différente de sujets / conversions. La conversion implicite a lieu n’importe où cette conversion a lieu sans votre mot à dire spécifique (pas de transtypage). Considérez-le comme suit: l’utilisation d’un casting énonce explicitement votre intention.

Promotion:

La promotion survient lorsque deux types ou plus interagissent dans une expression de taille différente. C’est un cas spécial de type “coercition”, sur lequel je reviendrai dans un instant. La promotion prend simplement le petit type et l’élargit au plus grand. Il n’y a pas de jeu standard de tailles pour les types numériques, mais en général, char

Coercition:

La contrainte se produit chaque fois que les types dans une expression ne correspondent pas. Le compilateur “contraindra” un type inférieur à un type supérieur. Dans certains cas, tels que la conversion d’un entier en un type double ou d’un type non signé en un type signé, des informations peuvent être perdues. La coercition inclut la promotion, de sorte que des types similaires de tailles différentes sont résolus de cette manière. Si la promotion ne suffit pas, les types intégraux sont convertis en types flottants et les types non signés en types signés. Cela se produit jusqu’à ce que tous les composants d’une expression soient du même type.

Ces actions du compilateur ne concernent que les types bruts et numériques. La coercition et la promotion ne se produisent pas pour les classes définies par l’utilisateur. En règle générale, la diffusion explicite ne fait aucune différence, sauf si vous inversez les règles de promotion / contrainte. Cela éliminera toutefois les avertissements du compilateur que la coercition provoque souvent.

Les types définis par l’utilisateur peuvent cependant être convertis. Cela se produit lors de la résolution de surcharge. Le compilateur trouvera les différentes entités qui ressemblent à un nom que vous utilisez, puis passera par un processus pour déterminer laquelle des entités doit être utilisée. La conversion “d’identité” est préférée avant tout; cela signifie qu’un f(t) résoudra en f(typeof_t) dessus tout le rest (voir Fonction avec un type de paramètre possédant un constructeur de copie avec un ref non-const choisi – pour une confusion qui peut en générer). Si la conversion d’identité ne fonctionne pas, le système passe ensuite par cette hiérarchie complexe de tentatives de conversion incluant (dans le bon ordre) une conversion en type de base (découpage), des constructeurs définis par l’utilisateur et des fonctions de conversion définies par l’utilisateur. Il existe des termes géniaux au sujet des références qui, généralement, ne vous importent pas et que je ne comprends pas tout à fait sans regarder de toute façon.

Dans le cas d’une conversion de type d’utilisateur, la conversion explicite fait une énorme différence. L’utilisateur qui a défini un type peut déclarer un constructeur comme “explicite”. Cela signifie que ce constructeur ne sera jamais pris en compte dans un processus tel que décrit ci-dessus. Pour appeler une entité de manière à utiliser ce constructeur, vous devez explicitement le faire en transtypant (notez que la syntaxe telle que std::ssortingng("hello") n’est pas, à proprement parler, un appel au constructeur, mais à la place. une dissortingbution “style fonction”).

Comme le compilateur examinera en silence les constructeurs et les surcharges de conversion de type lors de la résolution du nom, il est vivement recommandé de déclarer le premier comme “explicite” et d’éviter de créer le second. C’est parce que chaque fois que le compilateur fait quelque chose en silence, il y a de la place pour des bugs. Les utilisateurs ne peuvent pas garder à l’esprit tous les détails de l’arborescence de code dans son intégralité, pas même la scope actuelle (en particulier l’ajout de la recherche dans koenig), de sorte qu’ils peuvent facilement oublier certains détails qui font que leur code fait une action non intentionnelle due aux conversions. Exiger un langage explicite pour les conversions rend de tels accidents beaucoup plus difficiles à faire.

Pour les types entiers, consultez le livre Secure Coding n C and C ++ by Seacord, le chapitre sur les débordements d’entiers.

En ce qui concerne les conversions de types implicites, les livres Effective C ++ et More Effective C ++ seront très utiles.

En fait, vous ne devriez pas être un développeur C ++ sans les lire.