Pourquoi les `void * 'ne sont-ils pas implicitement castés en C ++?

30

En C, il n'est pas nécessaire de transtyper un void *vers un autre type de pointeur, il est toujours promu en toute sécurité. Cependant, en C ++, ce n'est pas le cas. Par exemple,

int *a = malloc(sizeof(int));

fonctionne en C, mais pas en C ++. (Remarque: je sais que vous ne devriez pas utiliser mallocen C ++, ou d'ailleurs new, et que vous devriez plutôt préférer les pointeurs intelligents et / ou la STL; cela est demandé uniquement par curiosité.) Pourquoi la norme C ++ n'autorise-t-elle pas cette conversion implicite, alors que la norme C le fait?

wolfPack88
la source
3
long *a = malloc(sizeof(int));Oups, quelqu'un a oublié de changer un seul type!
Doval
4
@Doval: C'est toujours facilement corrigé en utilisant à la sizeof(*a)place.
wolfPack88
3
Je crois que le point de @ ratchetfreak est que la raison pour laquelle C effectue cette conversion implicite est parce que mallocne peut pas retourner un pointeur sur le type alloué. newsi C ++ renvoie un pointeur sur le type alloué, le code C ++ correctement écrit n'aura jamais de void *s à convertir.
Gort le robot
3
En passant, ce n'est pas un autre type de pointeur. Seuls les pointeurs de données doivent s'appliquer.
Déduplicateur
3
@ wolfPack88 vous avez mal votre histoire. C ++ avaitvoid , C non . Lorsque ce mot-clé / idée a été ajouté à C, ils l'ont changé pour l'adapter aux besoins de C. Ce fut peu de temps après les types de pointeur ont commencé à être vérifiés à tout . Voyez si vous pouvez trouver la brochure de description K&R C en ligne, ou une copie vintage d'un texte de programmation C comme le C Primer de Waite Group . ANSI C était plein, de fonctionnalités rétroportées ou inspirées par C ++, et K&R C était beaucoup plus simple. Il est donc plus correct que C ++ ait étendu C tel qu'il existait à l'époque, et que le C que vous connaissez a été supprimé de C ++.
JDługosz

Réponses:

39

Parce que les conversions de types implicites sont généralement dangereuses et que C ++ adopte une position de sécurité plus sûre que C.

C autorise généralement les conversions implicites, même si la plupart des chances sont que la conversion est une erreur. C'est parce que C suppose que le programmeur sait exactement ce qu'il fait, et sinon, c'est le problème du programmeur, pas le problème du compilateur.

C ++ interdira généralement les choses qui pourraient potentiellement être des erreurs et vous obligera à indiquer explicitement votre intention avec un transtypage de type. C'est parce que C ++ essaie d'être convivial pour les programmeurs.

Vous vous demandez peut-être pourquoi il est convivial alors qu'il vous oblige à taper davantage.

Eh bien, vous voyez, toute ligne de code donnée, dans n'importe quel programme, dans n'importe quel langage de programmation, sera généralement lue beaucoup plus de fois qu'elle ne sera écrite (*). Ainsi, la facilité de lecture est beaucoup plus importante que la facilité d'écriture. Et lors de la lecture, le fait de faire ressortir des conversions potentiellement dangereuses au moyen de transtypages explicites aide à comprendre ce qui se passe et à avoir un certain niveau de certitude que ce qui se passe est en fait ce qui était censé se produire.

En outre, l'inconvénient d'avoir à taper la distribution explicite est trivial par rapport à l'inconvénient d'heures après des heures de dépannage pour trouver un bogue qui a été causé par une affectation erronée dont vous auriez pu être averti, mais qui ne l'ont jamais été.

(*) Idéalement, il ne sera écrit qu'une seule fois, mais il sera lu chaque fois que quelqu'un devra le réviser pour déterminer sa pertinence pour la réutilisation, et chaque fois qu'un dépannage est en cours, et chaque fois que quelqu'un doit ajouter du code à proximité, puis à chaque fois qu'il y a un dépannage du code à proximité, etc. Cela est vrai dans tous les cas, sauf pour les scripts "écrire une fois, exécuter, puis jeter", et il n'est donc pas étonnant que la plupart des langages de script aient une syntaxe qui facilite la facilité d'écriture avec un mépris total pour la facilité de lecture. Avez-vous déjà pensé que Perl est complètement incompréhensible? Tu n'es pas seul. Considérez ces langues comme des langues "en écriture seule".

Mike Nakis
la source
C est essentiellement une étape au-dessus du code machine. Je peux pardonner C pour des choses comme ça.
Qix
8
Les programmes (quelle que soit la langue) sont destinés à être lus. Les opérations explicites se distinguent.
Matthieu M.
10
Il convient de noter que les transtypages void*sont plus dangereux en C ++, car avec la façon dont certaines fonctionnalités OOP sont implémentées en C ++, le pointeur vers le même objet peut avoir une valeur différente selon le type de pointeur.
hyde
@MatthieuM. très vrai. Merci d'avoir ajouté cela, cela vaut la peine de faire partie de la réponse.
Mike Nakis
1
@MatthieuM .: Ah, mais vous ne voulez vraiment pas avoir à tout faire explicitement. La lisibilité n'est pas améliorée en ayant plus à lire. Bien que sur ce point l'équilibre soit clairement pour être explicite.
Déduplicateur
28

Voici ce que dit Stroustrup :

En C, vous pouvez implicitement convertir un void * en T *. C'est dangereux

Il présente ensuite un exemple de la façon dont le vide * peut être dangereux et dit:

... Par conséquent, en C ++, pour obtenir un T * d'un vide *, vous avez besoin d'un cast explicite. ...

Enfin, il note:

L'une des utilisations les plus courantes de cette conversion non sûre en C consiste à affecter le résultat de malloc () à un pointeur approprié. Par exemple:

int * p = malloc (taille de (int));

En C ++, utilisez le nouvel opérateur typesafe:

int * p = nouveau int;

Il va dans beaucoup plus de détails à ce sujet dans The Design and Evolution of C ++ .

La réponse se résume donc à: le concepteur de langage pense qu'il s'agit d'un modèle dangereux, et l'a donc rendu illégal et a fourni d'autres moyens d'accomplir ce à quoi le modèle était normalement utilisé.

Gort le robot
la source
1
En ce qui concerne l' mallocexemple, il convient de noter que malloc(évidemment) n'appellera pas un constructeur, donc s'il s'agit d'un type de classe pour lequel vous allouez de la mémoire, être implicitement converti en type de classe serait trompeur.
chbaker0
C'est une très bonne réponse, sans doute meilleure que la mienne. Je n'aime que légèrement l'approche «argument de l'autorité».
Mike Nakis
"new int" - wow, en tant que débutant en C ++, j'avais du mal à penser à un moyen d'ajouter un type de base au tas, et je ne savais même pas que vous pouviez le faire. -1 utilisation pour malloc.
Katana314
3
@MikeNakisFWIW, mon intention était de compléter votre réponse. Dans ce cas, la question était "pourquoi ont-ils fait cela", donc j'ai pensé que le concepteur en chef était justifié.
Gort le robot
11

En C, il n'est pas nécessaire de lancer un void * sur un autre type de pointeur, il est toujours promu en toute sécurité.

C'est toujours promu, oui, mais pas en toute sécurité .

C ++ désactive ce comportement précisément parce qu'il tente d'avoir un système de type plus sûr que C, et ce comportement n'est pas sûr.


Considérez en général ces 3 approches de conversion de type:

  1. forcer l'utilisateur à tout écrire, donc toutes les conversions sont explicites
  2. supposer que l'utilisateur sait ce qu'il fait et convertir tout type en tout autre
  3. implémenter un système de type complet avec des génériques de type sûr (modèles, nouvelles expressions), des opérateurs de conversion définis par l'utilisateur explicites et forcer les conversions explicites uniquement des choses que le compilateur ne peut pas voir sont implicitement sûres

Eh bien, 1 est moche et un obstacle pratique pour faire quoi que ce soit, mais pourrait vraiment être utilisé là où un grand soin est nécessaire. C a à peu près opté pour 2, qui est plus facile à implémenter, et C ++ pour 3, qui est plus difficile à implémenter mais plus sûr.

Inutile
la source
La route 3 a été longue et difficile, avec des exceptions ajoutées plus tard et des modèles plus tard.
JDługosz
Eh bien, les modèles ne sont pas vraiment nécessaires pour un système de type sûr complet: les langues basées sur Hindley-Milner s'entendent très bien sans elles, sans conversions implicites du tout et pas besoin d'écrire explicitement les types non plus. (Bien sûr, ces langages reposent sur l'effacement de type / la collecte des ordures / le polymorphisme de type supérieur, des choses que C ++ évite plutôt.)
leftaroundabout
1

Par définition, un pointeur vide peut pointer vers n'importe quoi. Tout pointeur peut être converti en un pointeur vide et ainsi, vous pourrez reconvertir en arrivant à la même valeur exacte. Cependant, les pointeurs vers d'autres types peuvent avoir des contraintes, telles que des restrictions d'alignement. Par exemple, imaginez une architecture où les caractères peuvent occuper n'importe quelle adresse mémoire, mais les entiers doivent commencer sur des limites d'adresse égales. Dans certaines architectures, les pointeurs entiers peuvent même compter 16, 32 ou 64 bits à la fois afin qu'un char * puisse en fait avoir un multiple de la valeur numérique de int * tout en pointant vers le même endroit en mémoire. Dans ce cas, la conversion à partir d'un vide * arrondirait en fait des bits qui ne peuvent pas être récupérés et ne sont donc pas réversibles.

En termes simples, le pointeur vide peut pointer vers n'importe quoi, y compris des choses vers lesquelles d'autres pointeurs peuvent ne pas être capables de pointer. Ainsi, la conversion en pointeur vide est sûre mais pas l'inverse.

roserez
la source