55. Les paramètres const ref et les fonctions membres const ▲
Ce chapitre explique comment les paramètres et les fonctions membres peuvent être marqués comme const afin d'être utilisés avec des variables immutables. Comme nous avons déjà parlé des paramètres const dans les chapitres précédents, certaines informations de ce chapitre seront pour vous des révisions de fonctionnalités que vous connaissez déjà.
Bien que les exemples de ce chapitre n'utilisent que des structures, les fonctions membres const s'appliquent également aux classes.
55-1. Les objets immutables ▲
Nous avons déjà vu qu'il n'est pas possible de modifier des variables immutables :
immutable momentLecture =
MomentDeLaJournee
(
15
, 0
);
momentLecture ne peut pas être modifiée~
:
momentLecture =
MomentDeLaJournee
(
16
, 0
); // Erreur de compilation
momentLecture.minute =
10
;
Le compilateur n'autorise en aucune façon la modification d'objets immutables.
55-2. Les paramètres ref qui ne sont pas const ▲
Nous avons déjà vu ce concept dans le chapitre sur les paramètres de fonction. Les paramètres marqués comme ref peuvent être modifiés par la fonction. C'est pour cette raison que même si la fonction ne modifie réellement le paramètre, le compilateur ne permet pas de passer des objets immuables par ces paramètres.
/* bien qu'elle ne soit pas modifiée, duree n'est pas marquée
comme 'const' */
int
totalSecondes
(
ref Duree duree)
{
return
60
*
duree.minute;
}
// ...
immutable tempsEchauffement =
Duree
(
3
);
totalSecondes
(
tempsEchauffement); // Erreur de compilation
Le compilateur ne permet pas de passer la variable immutable tempsEchauffement à la fonction totalSecondes parce que la fonction ne garantit pas que le paramètre ne sera pas modifié.
55-3. Les paramètres const ref ▲
const ref signifie que le paramètre ne sera pas modifié par la fonction :
int
totalSecondes
(
const
ref Duree duree)
{
return
60
*
duree.minutes;
}
// ...
immutable tempsEchauffement =
Duree
(
3
);
totalSecondes
(
tempsEchauffement); // compile désormais
Ces fonctions peuvent recevoir des objets immutables comme paramètres parce que l'immutabilité de l'
objet est vérifiée par le compilateur~
:
int
totalSecondes
(
const
ref Duree duree)
{
duree.minute =
7
; // Erreur de compilation
}
Une alternative à const ref est in ref. Comme nous le verrons dans un chapitre ultérieur, in signifie que le paramètre est seulement utilisé en lecture par la fonction, désactivant toute possibilité de modification.
int
totalSecondes
(
in
ref Duree duree)
{
//...
}
55-4. Fonctions membres non-const ▲
Comme nous l'avons vu avec la fonction MomentDeLaJournee.incrementer(), les objets peuvent également être modifiés par leurs fonctions membres. incrementer() modifie les membres de l'objet sur lesquels elle est appelée :
struct
MomentDeLaJournee
{
//...
void
incrementer
(
in
Duree duree)
{
minute +=
duree.minute;
heure +=
minute /
60
;
minute %=
60
;
heure %=
24
;
}
//...
}
auto
debut =
MomentDeLaJournee
(
5
, 30
);
debut.incrementer
(
Duree
(
30
)); // debut est modifié
Fonctions membres const
Certaines fonctions membres ne modifient pas les objets sur lesquels elles sont appelées. Par exemple, la fonction toString
(
)~
:
struct
MomentDeLaJournee
{
//...
string toString
(
)
{
return
format
(
"%02s:%02s"
, heure, minute);
}
//...
}
Puisque le propre de toString() est de représenter l'objet dans un format textuel, il ne devrait pas modifier l'objet.
Le fait qu'une fonction membre ne modifie pas l'objet est déclaré par le mot-clé const après la liste des paramètres :
struct
MomentDeLaJournee
{
// ...
string toString
(
) const
{
return
format
(
"%02s:%02s"
, heure, minute);
}
}
Ce const garantit que l'objet lui-même ne sera pas modifié par la fonction membre. En conséquence, la fonction membre toString() peut être appelée, même sur un objet immutable. Autrement, toString() ne serait pas appelée automatiquement :
struct
MomentDeLaJournee
{
// ...
// Conception de qualité inférieure,
// la fonction n'est pas marquée const
string toString
(
)
{
return
format
(
"%02s:%02s"
, heure, minute);
}
}
// ...
immutable debut =
MomentDeLaJournee
(
5
, 30
);
writeln
(
debut); // MomentDeLaJournee.toString() n'est pas appelé !
Le résultat du code précédent n'est pas la chaîne 05:30 comme on pourrait s'y attendre, ce qui indique qu'une fonction générique est appelée à la place de MomentDeLaJournee.toString() :
immutable
(
MomenDeLaJournee)(
5
, 30
)
Qui plus est, un appel explicite à toString() génère une erreur à la compilation :
auto
s =
debut.toString
(
); // ERREUR
Selon ce principe, toutes les fonctions membres toString() que nous avons définies au chapitre précédent présentent le même défaut de conception et devraient toutes être marquées const.
Le mot-clé const peut aussi être spécifié avant la définition de la fonction :
// identique au code ci-avant
const
string toString
(
)
{
return
format
(
"%02s:%02s"
, heure, minute);
}
Puisque cette version pourrait donner la fausse impression que le const se réfère au type retourné, je vous recommande de spécifier const après la liste de paramètres.
55-5. Fonctions membres inout ▲
Comme nous l'avons vu dans le chapitre sur les paramètres de fonction, inout transfère la mutabilité d'un paramètre au type retourné par la fonction.
De la même manière, une fonction membre inout transfère la mutabilité de l'objet courant au type retourné par la fonction.
import
std.stdio;
struct
Conteneur
{
int
[] elements;
inout
(
int
)[] premierePartie
(
size_t n) inout
{
return
elements[0
..n];
}
}
void
main
(
)
{
{
// un conteneur immutable
auto
conteneur =
immutable
(
Conteneur)(
[1
, 2
, 3
]);
auto
tranche =
conteneur.premierePartie
(
2
);
writeln
(
typeof
(
tranche).stringof);
}
{
// un conteneur const
auto
conteneur =
const
(
Conteneur)(
[1
, 2
, 3
]);
auto
tranche =
conteneur.premierePartie
(
2
);
writeln
(
typeof
(
tranche).stringof);
}
{
// un conteneur mutable
auto
conteneur =
Conteneur
(
[1
, 2
, 3
]);
auto
tranche =
conteneur.premierePartie
(
2
);
writeln
(
typeof
(
tranche).stringof);
}
}
Les trois tranches qui sont retournées par les trois objets ayant des mutabilités différentes sont cohérentes avec les objets qui les ont retournées :
immutable
(
int
)[]
const
(
int
)[]
int
[]
Parce qu'elle doit pouvoir être appelée sur des objets const comme sur des objets immutables, une fonction membre inout est compilée comme si elle était const.
55-6. Mode d'emploi▲
Pour donner la garantie qu'un paramètre n'est pas modifié par la fonction, marquez ce paramètre in, const ou const ref.
Marquez les fonctions membres qui ne modifient pas l'objet courant comme const.
struct
MomentDeLaJournee
{
// ...
string toString
(
) const
{
return
format
(
"%02s:%02s"
);
}
}
Cela rendra la structure (ou la classe) plus utile en supprimant une limitation inutile. Les exemples dans la suite de ce livre respecteront ce principe.