Pourquoi puis-je accéder aux variables privées dans le constructeur de copie?

88

J'ai appris que je ne peux jamais accéder à une variable privée, uniquement avec une fonction get dans la classe. Mais alors pourquoi puis-je y accéder dans le constructeur de copie?

Exemple:

Field::Field(const Field& f)
{
  pFirst = new T[f.capacity()];

  pLast = pFirst + (f.pLast - f.pFirst);
  pEnd  = pFirst + (f.pEnd - f.pFirst);
  std::copy(f.pFirst, f.pLast, pFirst);
}

Ma déclaration:

private:
  T *pFirst,*pLast,*pEnd;
roi démon
la source
Parce que le constructeur de copie est un membre de classe par défaut, tout comme certains autres.
DumbCoder
+ 53 / -0? Qui a voté pour cela? Sinon , comment voulez - vous copier eux?!? (Démystifions les non-alternatives: créez un getter de référence public pour chaque membre privé? Ensuite, ils ne sont pas du tout privés. Créez un const&getter public ou par valeur pour chacun? Ensuite, ils ne sont que 'write-private', & pour les valeurs gaspillent des ressources et échouent pour les membres non copiables.) Je suis déconcerté par un tel succès d'une question aussi vide, posant des questions sur la construction de copie tout en ignorant totalement ce que cela signifie, et aucune réponse n'utilise une logique de base pour la démystifier. Ils expliquent des technicités sèches, mais il y a une réponse beaucoup plus simple à une question aussi clignotante
underscore_d
8
@underscore_d, "Comment pourriez-vous les copier autrement?" est une réponse très étrange à mon avis. C'est comme répondre "comment fonctionne la gravité?" avec "comment les choses tomberaient autrement!" Confondre l'encapsulation au niveau de la classe avec l'encapsulation au niveau de l'objet est en fait assez courant. C'est drôle comme vous semblez penser que c'est une question stupide et que la réponse doit être évidente. Gardez à l'esprit que l'encapsulation en Smalltalk (sans doute le langage OO archétypal) fonctionne réellement au niveau de l'objet.
aioobe
@aioobe Bon point, merci. Mon commentaire est un peu extrême - peut-être que la machine à café était cassée ce jour-là. Je vous remercie de souligner pourquoi cette question serait populaire, en particulier parmi ceux qui viennent d'autres langues OO (et peut-être plus). En fait, on peut soutenir que mon commentaire était la chose qui a été "aveuglée", puisque j'écrivais du point de vue de quelqu'un qui programme principalement en C ++. Aussi, j'adore cette analogie de gravité!
underscore_d

Réponses:

33

À mon humble avis, les réponses existantes font un mauvais travail expliquant le «pourquoi» de cela - en se concentrant trop sur la réitération de ce comportement valide. "Les modificateurs d'accès fonctionnent au niveau de la classe, et non au niveau de l'objet." - Oui mais pourquoi?

Le concept global ici est que ce sont les programmeurs qui conçoivent, écrivent et maintiennent une classe qui doivent comprendre l'encapsulation OO souhaitée et être habilités à coordonner sa mise en œuvre. Donc, si vous écrivez class X, vous encodez non seulement comment un X xobjet individuel peut être utilisé par le code avec accès à celui-ci, mais aussi comment:

  • les classes dérivées sont capables d'interagir avec lui (via des fonctions virtuelles optionnellement pures et / ou un accès protégé), et
  • des Xobjets distincts coopèrent pour fournir les comportements prévus tout en respectant les post-conditions et les invariants de votre conception.

Ce n'est pas seulement le constructeur de copie non plus - un grand nombre d'opérations peuvent impliquer deux ou plusieurs instances de votre classe: si vous comparez, ajoutez / multipliez / divisez, copiez-construisez, clonez, affectez, etc. alors c'est souvent le cas que vous soit doit simplement avoir accès à des données privées et / ou protégées dans l'autre objet, soit vouloir qu'il permette une implémentation de fonction plus simple, plus rapide ou généralement meilleure.

Plus précisément, ces opérations peuvent souhaiter profiter d'un accès privilégié pour effectuer des actions telles que:

  • (les constructeurs de copie) utilisent un membre privé de l'objet "rhs" (côté droit) dans une liste d'initialisation, de sorte qu'une variable membre est elle-même construite par copie au lieu d'être construite par défaut (même si elle est légale) puis assignée aussi (encore une fois, si légal)
  • partager des ressources - descripteurs de fichiers, segments de mémoire partagée, shared_ptrs pour référencer des données, etc.
  • s'approprier des choses, par exemple auto_ptr<>"déplacer" la propriété vers l'objet en construction
  • copier le "cache" privé, l'étalonnage ou les membres d'état nécessaires pour construire le nouvel objet dans un état utilisable de manière optimale sans avoir à les régénérer à partir de zéro
  • copie / accès aux informations de diagnostic / trace conservées dans l'objet en cours de copie qui ne sont pas autrement accessibles via les API publiques, mais peuvent être utilisées par un objet d'exception ou une journalisation ultérieure (par exemple, quelque chose sur le moment / les circonstances où l'instance "d'origine" non construite par copie a été construit)
  • effectuer une copie plus efficace de certaines données: par exemple, les objets peuvent avoir, par exemple, un unordered_mapmembre mais uniquement exposer publiquement begin()et des end()itérateurs - avec un accès direct à size()vous pourrait permettre reserveune copie plus rapide; pire encore s'ils ne font qu'exposer at()et insert()et autrement throw....
  • recopier les références aux objets parents / coordination / gestion qui pourraient être inconnus ou en écriture seule pour le code client
Tony Delroy
la source
2
Je pense que le plus gros «pourquoi» est que ce serait une surcharge d'exécution énorme pour vérifier si this == otherchaque fois que vous accédez à other.xce que vous auriez à faire si les modificateurs d'accès fonctionnaient au niveau de l'objet.
aioobe
2
@aioobe Je pense que votre réponse devrait être beaucoup plus importante. La réponse de Whilel Tony est vraiment bonne et conceptuelle, si j'étais un parieur, je parierais que votre réponse est la véritable raison historique du choix. Non seulement c'est plus performant, mais c'est aussi beaucoup plus simple. Ce serait une excellente question pour Bjarne!
Nir Friedman
J'ai marqué votre réponse, car elle explique le contexte;)
démon
@demonking, je pense que les raisons données dans cette réponse expliquent pourquoi il est pratique de laisser les données privées être ouvertes à d'autres objets. Mais les modificateurs d'accès ne sont pas censés rendre les données suffisamment "ouvertement". Ils sont plutôt destinés à rendre les données suffisamment fermées pour l'encapsulation. (En termes de commodité, ce serait encore mieux si les variables privées étaient publiques!) J'ai mis à jour ma réponse avec une section qui, je pense, répond mieux au pourquoi réel .
aioobe
@aioobe: vieux commentaires, mais de toute façon ... "vérifier si à this == otherchaque fois que vous accédez other.x" - manque le point - si other.xc'était seulement accepté au moment de l'exécution quand équivalent à this.x, il n'y aurait pas beaucoup d'écriture de pointeur other.xen premier lieu; le compilateur pourrait aussi bien vous forcer à écrire if (this == other) ...this.x...pour tout ce que vous allez faire. Votre conception de la «commodité (encore plus si les variables privées étaient publiques)» passe également à côté de l'essentiel - la façon dont la norme est définie est suffisamment restrictive pour permettre une encapsulation correcte , mais pas inutilement gênante.
Tony Delroy
108

Les modificateurs d'accès fonctionnent au niveau de la classe et non au niveau de l'objet .

Autrement dit, deux objets de la même classe peuvent accéder les uns aux autres aux données privées.

Pourquoi:

Principalement en raison de l'efficacité. Ce serait une surcharge d'exécution non négligeable de vérifier si à this == otherchaque fois que vous accédez à other.xce que vous auriez à faire si les modificateurs d'accès fonctionnaient au niveau de l'objet.

C'est aussi un peu sémantiquement logique si vous y réfléchissez en termes de portée: "Quelle est la taille du code dois-je garder à l'esprit lors de la modification d'une variable privée?" - Vous devez garder à l'esprit le code de toute la classe, et c'est orthogonal à quels objets existent au moment de l'exécution.

Et c'est incroyablement pratique lors de l'écriture de constructeurs de copie et d'opérateurs d'affectation.

aioobe
la source
35

Vous pouvez accéder aux membres privés d'une classe à partir de la classe, même à ceux d'une autre instance.

Alexander Rafferty
la source
10

Pour comprendre la réponse, je voudrais vous rappeler quelques notions.

  1. Quel que soit le nombre d'objets que vous créez, il n'y a qu'une seule copie d'une fonction en mémoire pour cette classe. Cela signifie que les fonctions ne sont créées qu'une seule fois. Cependant, les variables sont séparées pour chaque instance de la classe.
  2. this Le pointeur est passé à chaque fonction lorsqu'il est appelé.

Maintenant, c'est grâce au thispointeur, que la fonction est capable de localiser les variables de cette instance particulière. peu importe si c'est privé ou public. il est accessible dans cette fonction. Maintenant, si nous passons un pointeur vers un autre objet de la même classe. en utilisant ce deuxième pointeur, nous pourrons accéder aux membres privés.

J'espère que ça répond à ta question.

Ali Zaib
la source
6

Le constructeur de copie est une fonction membre de la classe et, en tant que tel, a accès aux données membres de la classe, même celles déclarées comme «privées».

Bojan Komazec
la source
3
En tant que personne connaissant la langue, je comprends ce que vous voulez dire. Cependant, si je ne connaissais pas la langue, j'aurais pensé que vous ne faisiez que me répéter la question.
San Jacinto