Accesseurs et mutateurs en C++ : getters et setters expliqués

Accesseurs et mutateurs en C++ : getters et setters expliqués
Sommaire5 sections
  1. 01Pourquoi cacher une donnée membre
  2. 02L’accesseur : lire une donnée protégée
  3. 03Le mutateur : modifier une donnée protégée
  4. 04Le vrai intérêt d’un mutateur : contrôler la valeur avant de l’accepter
  5. 05Le piège d’une encapsulation seulement apparente

Une classe C++ garde le contrôle total sur ses données membres déclarées private : seules ses propres fonctions membres peuvent les lire ou les modifier. Deux types de fonctions se chargent de cet accès : les accesseurs pour lire, les mutateurs pour écrire.

Ce mécanisme porte un nom savant, l’encapsulation, mais l’idée derrière est simple : c’est vous, en tant qu’auteur de la classe, qui décidez comment vos données peuvent être lues ou changées, plutôt que de laisser n’importe quel morceau de code du programme y toucher directement.

Pourquoi cacher une donnée membre

Une donnée membre private reste invisible depuis l’extérieur de la classe : aucune autre partie du programme ne peut la lire ou l’écrire sans passer par une fonction que vous avez vous-même écrite. Deux catégories de fonctions remplissent ce rôle :

  • les accesseurs, aussi appelés getters, qui renvoient la valeur d’une donnée membre ;
  • les mutateurs, aussi appelés setters, qui modifient cette valeur.

L’accesseur : lire une donnée protégée

Un accesseur doit renvoyer le type de la donnée qu’il expose, et n’a en général pas besoin d’argument. La convention veut qu’on le nomme en commençant par Get, pour que sa fonction saute aux yeux dès la lecture du code. Réduit à sa plus simple expression, il ressemble à ceci pour une donnée membre nommée age :

Déclaration dans la classeDéfinition de la fonction
private : int age ; public : int GetAge() ; int Toto::GetAge() { return age ; }

Cette fonction ne fait rien d’autre que renvoyer la valeur stockée. C’est volontaire : un accesseur sert à lire, pas à transformer la donnée au passage.

Le mutateur : modifier une donnée protégée

Un mutateur reçoit en paramètre la valeur à assigner, du même type que la donnée membre concernée, et ne renvoie généralement rien (type void). La convention de nommage veut qu’on le préfixe par Set. Sur le même exemple, un mutateur minimal ressemble à ceci :

Déclaration dans la classeDéfinition de la fonction
private : int _age ; public : void SetAge(int) ; void Toto::SetAge(int age) { _age = age ; }

Le vrai intérêt d’un mutateur : contrôler la valeur avant de l’accepter

L’avantage d’un mutateur ne tient pas seulement à l’encapsulation : c’est l’endroit idéal pour vérifier qu’une valeur est acceptable avant de l’assigner. Un âge ne devrait jamais être négatif, ni dépasser une limite raisonnable ; le mutateur peut tester cette condition et refuser l’affectation si elle n’est pas respectée, plutôt que de laisser une donnée incohérente s’installer silencieusement dans l’objet :

Version avec contrôleComportement
int Toto::SetAge(int age) { if (age &lt ; 200) { _age = age ; return 1 ; } else return 0 ; } Renvoie 1 si la valeur a été acceptée, 0 sinon, sans jamais modifier _age avec une valeur jugée invalide.

Le piège d’une encapsulation seulement apparente

Un accesseur ou un mutateur mal écrit peut donner une fausse impression de sécurité. Un accesseur qui renvoie directement une référence vers une donnée interne complexe, par exemple, permet en réalité de la modifier de l’extérieur sans passer par le mutateur, ce qui annule tout l’intérêt de l’encapsulation. La règle reste simple : un accesseur renvoie une copie ou une valeur en lecture seule, un mutateur passe par un test avant d’accepter le changement. C’est cette discipline, plus que la simple présence de Get et Set dans le nom des fonctions, qui fait la différence entre une classe bien conçue et une classe qui ne fait que déplacer le problème d’un mot-clé private vers un autre.

Ce mécanisme fait partie des bases de la programmation par objets, aux côtés de l’héritage et du polymorphisme, et se retrouve avec des noms et des syntaxes différentes dans la quasi-totalité des langages orientés objet, de Java à Python en passant par C#.

Résumer cet article avec :

Partager :