Qui a le droit de toucher à vos données
Quand vous avez écrit votre première classe, vous avez probablement exposé chaque membre au monde extérieur. Cela fonctionne, mais cela gâche l'une des principales raisons d'être des classes : l'encapsulation - cacher l'état interne d'une classe pour que le reste du programme ne puisse interagir avec elle qu'à travers une surface contrôlée. Les spécificateurs d'accès sont la façon dont vous tracez cette frontière.
Il y en a exactement trois : public, private et protected. Chacun étiquette les membres qui le suivent, et l'étiquette décide quel code est autorisé à les lire ou les écrire. Faites-le bien et votre classe applique ses propres règles ; faites-le mal et n'importe quel bug n'importe où peut corrompre l'état de votre objet.
Les trois spécificateurs
Un spécificateur est un mot-clé suivi de deux-points. Tout membre déclaré après lui - jusqu'au spécificateur suivant - relève de ce niveau d'accès.
Décommentez la dernière ligne et le compilateur refuse de construire : balance est private, donc main ne peut pas y toucher directement. C'est là tout l'intérêt - la seule façon de modifier le solde est via deposit, ce qui signifie que vous pourrez plus tard ajouter une validation (pas de dépôts négatifs, journalisation, limites) à un seul endroit et avoir l'assurance qu'elle est toujours appliquée.
Voici le détail complet :
// accessible depuis...
// public partout (tout code qui possède l'objet)
// private uniquement les propres membres de la classe (+ friends)
// protected les propres membres de la classe ET les classes dérivées (+ friends)
class par rapport à struct : la valeur par défaut
Vous pouvez écrire autant de blocs de spécificateurs que vous voulez, dans n'importe quel ordre. Ce que les membres obtiennent avant que vous n'écriviez le premier dépend de l'usage de class ou de struct :
- Dans une
class, les membres sontprivatepar défaut. - Dans un
struct, les membres sontpublicpar défaut.
Cette valeur par défaut est la seule différence au niveau du langage entre les deux mots-clés. Un struct peut avoir des méthodes, des constructeurs et des sections private tout comme une class.
La convention est d'utiliser struct pour de simples regroupements de données publiques et class quand vous voulez du comportement et un état caché - mais le compilateur ne l'impose pas, seule la valeur par défaut diffère.
Encapsulation avec getters et setters
Le schéma quotidien est le suivant : les données vont dans une section private, et une méthode public donne un accès contrôlé. Un getter en lecture seule renvoie la valeur ; un setter valide avant d'affecter. C'est là que private porte ses fruits.
Comme celsius est private, il n'y a aucun moyen d'y glisser une valeur invalide - chaque écriture doit passer par setCelsius, qui protège l'invariant. Remarquez que les getters sont marqués const : ils promettent de ne pas modifier l'objet, vous pouvez donc aussi les appeler sur des objets const Temperature.
protected et l'héritage
protected ne compte qu'une fois que l'héritage entre en jeu. Il se comporte comme private pour le code extérieur, mais une classe dérivée peut y accéder. Utilisez-le pour des membres dont une sous-classe a légitimement besoin mais que le public ne devrait toujours pas atteindre.
Une erreur fréquente de débutant est de recourir à protected sur chaque membre de données « au cas où une sous-classe en aurait besoin ». Cela élargit silencieusement le contrat de votre classe - désormais chaque sous-classe peut dépendre de ce champ, et vous ne pouvez plus le modifier librement. Préférez private et ne passez à protected que lorsqu'une classe dérivée a réellement besoin de l'accès.
L'échappatoire friend
Parfois, une seule fonction ou classe extérieure a légitimement besoin de voir vos rouages internes - un cas classique est un opérateur comme << que vous ne pouvez pas faire membre. Le mot-clé friend accorde à cette unique entité nommée l'accès à vos membres private et protected, et à rien d'autre.
friend est une exception délibérée et chirurgicale - la classe elle-même nomme exactement à qui elle fait confiance, de sorte que personne ne peut s'accorder l'accès depuis l'extérieur. Utilisez-le avec parcimonie ; si vous vous retrouvez à ajouter beaucoup de friends, c'est probablement que vos membres n'auraient pas dû être private au départ, ou que votre conception doit être repensée.
Erreurs courantes à éviter
- Rendre chaque membre
public. Ça paraît simple, mais vous perdez toute la validation et les invariants que l'encapsulation vous offre. Par défaut, donnéesprivateavec méthodespublic. - Oublier la valeur par défaut de
class.class Foo { int x; };rendxprivate, doncfoo.x = 5ne compilera pas. Si vous vouliez un simple regroupement de données, utilisezstructou ajoutez une étiquettepublic:. - Abuser de
protected. C'est une frontière plus faible queprivateet elle n'est pertinente qu'avec l'héritage. Y recourir partout couple les sous-classes à des champs que vous pourriez vouloir modifier. - S'attendre à ce que
privatesoit une fonctionnalité de sécurité. C'est une règle de compilation qui empêche l'accès accidentel, pas du chiffrement. Les octets sont toujours en mémoire ;privateconcerne une conception propre, pas le secret.
Suite : Structs
Vous avez désormais vu qu'un struct n'est en réalité qu'une class dont les membres sont public par défaut. La page suivante, structs, creuse les cas où cette valeur public-par-défaut est exactement ce que vous voulez - de légers agrégats pour regrouper des valeurs liées - et comment struct est utilisé dans le C++ idiomatique aux côtés de classes complètes.
Questions fréquentes
Quelle est la différence entre public, private et protected en C++ ?
Les membres public sont accessibles de partout. Les membres private ne sont accessibles que depuis l'intérieur de la même classe (et de ses friends). protected est comme private, mais permet en plus aux classes dérivées d'atteindre le membre. Par convention, on garde les données private et on expose le comportement via des méthodes public.
Les membres sont-ils privés par défaut en C++ ?
Dans une class, oui - tout est private jusqu'à ce que vous écriviez un spécificateur d'accès. Dans un struct, la valeur par défaut est public. Cette unique valeur par défaut est la seule véritable différence entre class et struct en C++ ; les deux peuvent avoir des méthodes, des constructeurs et des spécificateurs d'accès.
Que fait le mot-clé friend en C++ ?
friend accorde à une fonction ou une classe précise l'accès à vos membres private et protected. C'est une exception délibérée et étroite à l'encapsulation - la classe nomme exactement à qui elle fait confiance, de sorte que l'accès n'est jamais accordé implicitement.