Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Quel opérateur de cast ?

23 réponses
Avatar
meow
Hello,

Plutot que de relire pour la dixi=E8me fois le 30 chapitres et
tutoriaux sur les cast, auxquels je n'ai toujours rien pann=E9, j'ai
d=E9cid=E9 de changer de technique : je pose la question sur un cas
concret et j'affinerai ma connaissance au fur et =E0 mesure. Dans
l'extrait de code :

Volumetric* create(Kind k){
Volumetric* v;
switch (k) {
case RAW:
v =3Dnew RawVolumetric;break;
default:
v =3DNULL;break;
}
return v;
}

il y a un v=3DNULL qui ne me semble pas tr=E8s joli. Il faudrait tout du
moins que je le caste en Volumetric*, non ? Et si oui, quel operateur
dois-je utiliser ?

--Ben

10 réponses

1 2 3
Avatar
Michel Decima
Hello,

Plutot que de relire pour la dixième fois le 30 chapitres et
tutoriaux sur les cast, auxquels je n'ai toujours rien panné, j'ai
décidé de changer de technique : je pose la question sur un cas
concret et j'affinerai ma connaissance au fur et à mesure. Dans
l'extrait de code :

Volumetric* create(Kind k){
Volumetric* v;
switch (k) {
case RAW:
v =new RawVolumetric;break;
default:
v =NULL;break;
}
return v;
}

il y a un v=NULL qui ne me semble pas très joli.


Tu peux remplacer par v=0, c'est equivalent.

Il faudrait tout du moins que je le caste en Volumetric*, non ?


non.

Et si oui, quel operateur dois-je utiliser ?


Avatar
meow
Tu peux remplacer par v=0, c'est equivalent.


Euh... C'est pas dangereux ? Je veux dire, jusqu'à présent, tous les
NULL que j'ai vu valaient effectivement 0, mais bon, j'imagines que si
on a inventé NULL c'est justement pour s'abstraire de la valeur, non ?

Il faudrait tout du moins que je le caste en Volumetric*, non ?
non.



O_o
Mon compilo dit la meme chose que toi, mais je ne comprends pas bien.
Pour moi, NULL est (void*) par défaut... Ou en tout cas, tout sauf
typé.


Avatar
Sylvain Togni
Tu peux remplacer par v=0, c'est equivalent.


Euh... C'est pas dangereux ? Je veux dire, jusqu'à présent, tous les
NULL que j'ai vu valaient effectivement 0, mais bon, j'imagines que si
on a inventé NULL c'est justement pour s'abstraire de la valeur, non ?

Il faudrait tout du moins que je le caste en Volumetric*, non ?
non.



O_o
Mon compilo dit la meme chose que toi, mais je ne comprends pas bien.
Pour moi, NULL est (void*) par défaut... Ou en tout cas, tout sauf
typé.


En C++, NULL est de type entier, car il n'y a pas de conversion
void* -> T*, contrairement au C.

Il est le plus souvent défini ainsi:

#define NULL 0

Ce qui fait qu'utiliser NULL ou 0 est une affaire de goût.

--
Sylvain Togni



Avatar
Fabien LE LEZ
On 13 Jul 2006 08:37:41 -0700, "meow" :

Volumetric* create(Kind k){
Volumetric* v;


Ça ne répond pas directement à ta question, mais j'aurais tendance à
mettre le 0/NULL ici :


Volumetric* create(Kind k)
{
Volumetric* v= 0;


D'une manière générale, je n'aime pas avoir une variable non
initialisée.

En prime, ici, on a une valeur par défaut toute trouvée (0/NULL),
qu'on ne modifie que si on a effectivement une nouvelle valeur.

La fonction devient alors :

Volumetric* create (Kind k)
{
Volumetric* v= 0;

switch (k)
{
case RAW: v= new RawVolumetric;break;
}
return v;
}

Note qu'à tout moment, v est un pointeur valide, sur lequel on peut
appeler delete sans problème.




J'aurais tendance à écrire

Volumetric* create (Kind k)
{
switch (k)
{
case RAW: return new RawVolumetric;
}
return 0;
}

mais certains n'aiment pas les fonctions avec plusieurs "return" ;
j'avoue ne pas trop savoir pourquoi.

Avatar
Arnaud Meurgues
meow wrote:

Il faudrait tout du moins que je le caste en Volumetric*, non ?
non.

O_o

Mon compilo dit la meme chose que toi, mais je ne comprends pas bien.
Pour moi, NULL est (void*) par défaut...


Ce que dit la norme qui vaut sans doute mieux qu'un long discours :

4.10/1
A null pointer constant is an integral constant expression (5.19) rvalue
of integer type that evaluates to zero. A null pointer constant can be
converted to a pointer type; the result is the null pointer value of
that type and is distinguishable from every other value of pointer to
object or pointer to function type. Two null pointer values of the same
type shall compare equal. The conversion of a null pointer constant to a
pointer to cvqualified type is a single conversion, and not the sequence
of a pointer conversion followed by a qualification conversion (4.4).

4.11/1
A null pointer constant (4.10) can be converted to a pointer to member
type; the result is the null member pointer value of that type and is
distinguishable from any pointer to member not created from a null
pointer constant. Two null member pointer values of the same type shall
compare equal. The conversion of a null pointer constant to a pointer to
member of cvqualified type is a single conversion, and not the sequence
of a pointer to member conversion followed by a qualification conversion
(4.4)

18.1/4
The macro NULL is an implementation-defined C++ null pointer constant in
this International Standard (4.10).

--
Arnaud



Avatar
Loïc Joly

mais certains n'aiment pas les fonctions avec plusieurs "return" ;
j'avoue ne pas trop savoir pourquoi.



Dans un langage qui n'aiderait pas à nettoyer des données en sortie de
code (comme peuvent le faire les destructeurs du C++, les using(){} du
C#, les try/finally de Java), je n'aimerai pas non plus le code à
plusieurs return. Ni les exceptions (qui sont un cas similaire).

Je pesne que les tenants du SESE ont fait leurs premières armes avec des
langages comme C, où ça a un intérêt certain.

--
Loïc

Avatar
James Kanze
meow wrote:

Plutot que de relire pour la dixième fois le 30 chapitres et
tutoriaux sur les cast, auxquels je n'ai toujours rien panné, j'ai
décidé de changer de technique : je pose la question sur un cas
concret et j'affinerai ma connaissance au fur et à mesure. Dans
l'extrait de code :


Volumetric* create(Kind k){
Volumetric* v;
switch (k) {
case RAW:
v =new RawVolumetric;break;
default:
v =NULL;break;
}
return v;
}


il y a un v=NULL qui ne me semble pas très joli.


Pourquoi ? Il faut bien lui affecter le pointeur null, non ?

Il faudrait tout du moins que je le caste en Volumetric*, non ?


Non. Mais la raison est un peu perverse. Grosso modo :

-- En C++, il y a un concepte d'une constante de pointeur null (un
« null pointer constant ». Malgré son nom, il n'est pas un
pointeur, mais une « expression constante entière dont la valeur
est 0 ». Il peut être donc « 0 », « 1 - 1 » ou n'importe quelle
autre expression qui a un type entier, et qui s'évalue à 0.

Ce qui caractèrise cette expression, c'est qu'elle se convertit
implicitement en n'importe quel type de pointeur, et que le résultat
de cette conversion, c'est un pointeur null. Note bien que pour que
ça a lieu, il faut que toutes les exigences soient remplies : que
l'expression soit conforme à ce que le langage considère une
expression entière constante, et qu'elle s'évalue à 0. (Il y a eu
une discussion récemment dans le groupe anglophone, du fait que
« (0, 0) » n'est pas, malgré les apparences, une expression
constante entière.)

Note bien aussi qu'il n'y a aucune exigeance qu'un pointeur null ait
tous les bits à zéro. Il s'agit ici d'une conversion, et c'est le
problème du compilateur à faire en sorte que ça marche correctement.
(Note bien aussi que si tu fais un « reinterpret_cast » d'une
variable de type int, dont la valeur est 0, ce n'est pas garantie
que tu ais le même résultat que si tu as une expression constante
qui vaut 0.)

-- La macro NULL est guarantie à répresenter une constante de pointeur
null. Souvent, c'est simplement « 0 », mais sur un bon
compilateur, ça serait quelque chose du genre __nullptr, de façon à
générer un avertissement si tu t'en sers comme entier. Selon la
norme :
int i = NULL ;
est légal, mais je connais personne qui le considère bien.

-- La situation est, comme j'ai dit, un peu perverse. Quand on connaît
les origines de C, on comprend comment on y est arrivé, mais dans un
langage fortement typé comme le C++, c'est vraiment une aberration.
Aussi y'a-t-il une proposition pour créer un véritable nullptr, qui
a, je crois, de fortes chances de faire partie de la prochaine
version de C++.

Et si oui, quel operateur dois-je utiliser ?


Ici, je dirais aucun.

Ici. Parce que le type de la constante de pointeur null n'est pas ce
qu'on pourrait s'attendre (et parce que de toute façon, il ne pourrait
pas avoir le type de tous les pointeurs à la fois), il y a des contextes
où il faut une conversion explicite. Celui qui me vient tout de suite à
l'esprit, c'est quand on le passe à une fonction « varargs », c-à-d
quand il correspond à un paramètre désigné par « ... ». Dans ce
cas-là, le compilateur ne connaît pas le type voulu, et est obligé à
croire au type donné, c-à-d int (ou un autre type entier). Il faut donc
explicitement préciser le type voulu. Dans ces cas-là, c'est le
static_cast qui convient.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34

Avatar
James Kanze
Fabien LE LEZ wrote:
On 13 Jul 2006 08:37:41 -0700, "meow" :


Volumetric* create(Kind k){
Volumetric* v;



Ça ne répond pas directement à ta question, mais j'aurais tendance à
mettre le 0/NULL ici :


Volumetric* create(Kind k)
{
Volumetric* v= 0;


D'une manière générale, je n'aime pas avoir une variable non
initialisée.


En prime, ici, on a une valeur par défaut toute trouvée (0/NULL),
qu'on ne modifie que si on a effectivement une nouvelle valeur.


Tout à fait d'accord.

La fonction devient alors :


Volumetric* create (Kind k)
{
Volumetric* v= 0;

switch (k)
{
case RAW: v= new RawVolumetric;break;
}
return v;
}


Note qu'à tout moment, v est un pointeur valide, sur lequel on peut
appeler delete sans problème.


Ici, ce n'est pas un problème, parce qu'il s'agit d'une variable locale,
mais dans le cas général, c'est réconfortant à savoir que même si le new
lève une exception, le pointeur a une valeur légitime.

J'aurais tendance à écrire


Volumetric* create (Kind k)
{
switch (k)
{
case RAW: return new RawVolumetric;
}
return 0;
}


mais certains n'aiment pas les fonctions avec plusieurs "return" ;
j'avoue ne pas trop savoir pourquoi.


Parce qu'on aime pouvoir raisonner de façon rigueureuse sur la
correction du code.

Ce qui ne veut pas dire qu'on n'admet pas d'exception. Dans un cas comme
ceci, j'admets volentiers quelque chose du genre :

Volumetric*
create( Kind k )
{
switch ( k )
{
case RAW :
return new RawVolumetric ;

// ...

default :
return NULL ;
}
}

Mais je le limite aux cas où :

-- la seule instruction dans la fonction est le switch,
-- tous les cas du switch contient une seule instruction, qui est le
return, et
-- il y a un default.

Aussi, dans l'ensemble, ce n'est pas si utile que ça, parce que les
switch sont assez rare. Bien plus souvent, j'aurais quelque chose du
genre :

Volumetric*
create( Kind k )
{
Map::const_iterator iter = map.find( k ) ;
return iter == map.end()
? NULL
: iter->second->create() ;
}

L'avantage, évidemment, c'est que je peux ajouter des types sans toucher
au code existant.

--
James Kanze
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France +33 (0)1 30 23 00 34


Avatar
Fabien LE LEZ
On Thu, 13 Jul 2006 22:47:38 +0200, James Kanze :

(Il y a eu
une discussion récemment dans le groupe anglophone, du fait que
« (0, 0) » n'est pas, malgré les apparences, une expression
constante entière.)


Tiens ? Qu'est-ce donc, alors ?

(Note bien aussi que si tu fais un « reinterpret_cast » d'une
variable de type int, dont la valeur est 0, ce n'est pas garantie
que tu ais le même résultat que si tu as une expression constante
qui vaut 0.)


Si je ne m'abuse, on peut généraliser : si tu fais un
"reinterpret_cast<T*>(i)", où "i" est un nombre, tu n'as absolument
aucune garantie...

Avatar
Fabien LE LEZ
On Thu, 13 Jul 2006 22:56:53 +0200, James Kanze :

mais certains n'aiment pas les fonctions avec plusieurs "return" ;
j'avoue ne pas trop savoir pourquoi.


Parce qu'on aime pouvoir raisonner de façon rigueureuse sur la
correction du code.

Ce qui ne veut pas dire qu'on n'admet pas d'exception.


Justement, parlons-en, des exceptions... (Au sens C++, pour le coup.)

Le mécanisme des exceptions fait que de toutes façons, dans la plupart
des fonctions non triviales, il y a une possibilité pour qu'on sorte
de la fonction par un autre endroit que le "return".
Par conséquent, la durée de vie des objets locaux, et la validité des
objets "externes", ne doit pas se baser sur le principe SESE.

D'autre part, il me semble que le compilateur se charge de s'assurer
que quelque soit le cheminement dans la fonction, une valeur de retour
est bien renvoyée.

Du coup, je ne vois pas bien ce que se forcer à mettre un seul
"return", avec toutes les circonvolutions que ça amène parfois,
apporte réellement.

Au fait, question subsidiaire : j'ai toujours entendu parler de
"single entry, single exit", et j'avoue que je ne saisis pas bien le
"single entry" : on peut entrer dans une fonction par un autre endroit
que le début ? Ou l'expression est comme ça par pur amour de la
symétrie ?


1 2 3