Cours complet pour apprendre à programmer en D


précédentsommairesuivant

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 :

 
Sélectionnez
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.

 
Sélectionnez
/* 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 :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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() :

 
Sélectionnez
immutable(MomenDeLaJournee)(5, 30)

Qui plus est, un appel explicite à toString() génère une erreur à la compilation :

 
Sélectionnez
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 :

 
Sélectionnez
// 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.

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+