programmez-en-d/fonctions_membres_const.whata

244 lines
8.6 KiB
Plaintext

[set
title = "Les paramètres ``const ref`` et les fonctions membres ``const``"
partAs = chapitre
translator = "Olivier Pisano"
]
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 ``immutable``. 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.
[ = Les objets ``immutable``
Nous avons déjà vu qu'il n'est pas possible de modifier des variables immutable~ :
[code=d <<<
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 ``immutable``.
]
[ = 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.
[code=d <<<
/* 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 immuable ``tempsEchauffement`` à la fonction ``totalSecondes`` parce que la fonction ne garantie pas que le paramètre ne sera pas modifié.
]
[ = Les paramètres ``const ref``
``const ref`` signifie que le paramètre ne sera pas modifié par la fonction~ :
[code=d <<<
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 immutable 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.
[code=d <<<
int totalSecondes(in ref Duree duree)
{
//...
}
>>>]
]
[ = Fonctions membres non-``const``
Comme nous l'avons vu avec la fonction ``MomentDeLaJournee.incrementer()``, les objets peuvent également être modifiés par leur fonctions membres. ``incrementer()`` modifie les membres de l'objet sur lesquels elle est appelée~ :
[code=d <<<
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 membre ne modifient pas les objets sur lequels 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~ :
[code=d <<<
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~ :
[code=d <<<
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()``~ :
[code=d <<<
immutable(MomenDeLaJournee)(5, 30)
>>>]
Qui plus est, un appel explicite à ``toString()`` génère une erreur à la compilation~ :
[code=d <<<
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ée const.
[p Note~ : | Le mot-clé const peut aussi être spécifié avant la définition de la fonction~ :]
[code=d <<<
// 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.
]
[ = 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.
[code=d <<<
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~ :
[code=d <<<
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``.
]
[ = 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.
[code=d <<<
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.
]