¿Por qué puedo acceder a variables privadas en el constructor de copias?

88

Aprendí que nunca puedo acceder a una variable privada, solo con una función get en la clase. Pero entonces, ¿por qué puedo acceder a él en el constructor de copias?

Ejemplo:

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);
}

Mi declaración:

private:
  T *pFirst,*pLast,*pEnd;
Rey Demonio
fuente
Porque el constructor de copia es un miembro de la clase por defecto, y también lo son algunos otros.
DumbCoder
+ 53 / -0? ¿Quién votó por esto? ¿De qué otra manera los copiarías ? (Vamos a desacreditar las no alternativas: ¿hacer un getter de referencia público para cada miembro privado? Entonces no son privados en absoluto. ¿Hacer un const&getter público o por valor para cada uno? Entonces solo son 'escritura-privados', & para los valores desperdician recursos y fallan para los miembros que no se pueden copiar.) Estoy desconcertado por el éxito de una pregunta tan vacía, preguntando sobre la construcción de copias mientras ignora totalmente lo que significa, y ninguna respuesta usa la lógica básica para desacreditarla. Explican tecnicismos secos, pero hay una respuesta mucho más simple a una pregunta así de ciega
underscore_d
8
@underscore_d, "¿De qué otra manera los copiarías?" es una respuesta muy extraña en mi opinión. Es como responder "¿cómo funciona la gravedad?" con "¡de qué otra manera se derrumbarían las cosas!" De hecho, es bastante común confundir la encapsulación a nivel de clase con la encapsulación a nivel de objeto. Es curioso cómo parece pensar que es una pregunta estúpida y que la respuesta debería ser obvia. Tenga en cuenta que la encapsulación en Smalltalk (posiblemente el lenguaje arquetípico de OO) en realidad funciona a nivel de objeto.
aioobe
@aioobe Buen punto, gracias. Mi comentario es un poco extremo, tal vez la máquina de café se rompió ese día. Le agradezco que señale por qué esta pregunta sería popular, especialmente entre aquellos que provienen de otros (y quizás más) idiomas OO. De hecho, se puede argumentar que mi comentario fue "ciego", ya que estaba escribiendo desde la perspectiva de alguien que programa principalmente en C ++. Además, ¡me encanta esa analogía de la gravedad!
underscore_d

Respuestas:

33

En mi humilde opinión, las respuestas existentes no hacen un buen trabajo al explicar el "por qué" de esto, centrándose demasiado en reiterar qué comportamiento es válido. "Los modificadores de acceso funcionan a nivel de clase y no a nivel de objeto". - ¿si, pero por qué?

El concepto general aquí es que son los programadores que diseñan, escriben y mantienen una clase los que se espera que comprendan la encapsulación OO deseada y estén capacitados para coordinar su implementación. Entonces, si está escribiendo class X, está codificando no solo cómo un X xobjeto individual puede ser utilizado por código con acceso a él, sino también cómo:

  • las clases derivadas pueden interactuar con él (a través de funciones virtuales puras opcionalmente y / o acceso protegido), y
  • los Xobjetos distintos cooperan para proporcionar comportamientos previstos mientras respetan las condiciones posteriores y las invariantes de su diseño.

Tampoco es solo el constructor de copia; muchas operaciones pueden involucrar dos o más instancias de su clase: si está comparando, sumando / multiplicando / dividiendo, copiando-construyendo, clonando, asignando, etc., entonces a menudo es el caso que usted o simplemente debe tener acceso a datos privados y / o protegidos en el otro objeto, o quiere que permita una implementación de función más simple, rápida o generalmente mejor.

Específicamente, estas operaciones pueden querer aprovechar el acceso privilegiado para hacer cosas como:

  • (constructores de copia) usan un miembro privado del objeto "rhs" (lado derecho) en una lista de inicializador, de modo que una variable miembro se construye con copia en lugar de construirse por defecto (si es que es legal) y luego se asigna también (nuevamente, si es legal)
  • recursos compartidos: identificadores de archivos, segmentos de memoria compartida, shared_ptr s para datos de referencia, etc.
  • tomar posesión de las cosas, por ejemplo auto_ptr<> "traslada" la propiedad al objeto en construcción
  • copiar "caché" privado, calibración o miembros de estado necesarios para construir el nuevo objeto en un estado de uso óptimo sin tener que regenerarlos desde cero
  • copiar / acceder a la información de diagnóstico / seguimiento que se mantiene en el objeto que se está copiando que no es accesible de otra manera a través de API públicas, pero que podría ser utilizada por algún objeto de excepción o registro posterior (por ejemplo, algo sobre el tiempo / circunstancias en que la instancia "original" no construida por copia fue construido)
  • realizar una copia más eficiente de algunos datos: p. ej., los objetos pueden tener, p. ej., un unordered_mapmiembro pero solo exponer begin()e end()iteradores públicamente - con acceso directo a size()su reservecapacidad de copia más rápida; peor aún si solo exponen at()y insert()y de lo contrario throw....
  • copiar referencias a objetos principales / de coordinación / administración que pueden ser desconocidos o de solo escritura para el código del cliente
Tony Delroy
fuente
2
Creo que el mayor 'por qué' es que sería una tremenda sobrecarga de tiempo de ejecución comprobar si this == othercada vez que accede a other.xcuál tendría que hacerlo si los modificadores de acceso funcionaran a nivel de objeto.
aioobe
2
@aioobe Creo que tu respuesta debería ser mucho, mucho más prominente. Si bien la respuesta de Tony es realmente buena y conceptual, si yo fuera un hombre de apuestas, apostaría a que su respuesta es la razón histórica real de la elección. No solo es más eficiente, sino que también es mucho más simple. ¡Sería una gran pregunta para Bjarne!
Nir Friedman
He marcado tu respuesta, porque explica el trasfondo;)
demonking
@demonking, creo que las razones dadas en esta respuesta cubren por qué es conveniente dejar que los datos privados estén abiertos a otros objetos. Pero los modificadores de acceso no están destinados a hacer que los datos sean lo suficientemente "abiertos". Más bien están destinados a hacer que los datos estén lo suficientemente cerrados para la encapsulación. (¡En términos de conveniencia , sería incluso mejor si las variables privadas fueran públicas!) Actualicé mi respuesta con una sección que creo que aborda mejor el por qué real .
aioobe
@aioobe: comentarios antiguos, pero de todos modos ... "comprueba si this == othercada vez que accedes other.x" - pierde el sentido - si other.xsolo se aceptara en tiempo de ejecución cuando es equivalente a this.x, no habría mucha escritura de puntero other.xen primer lugar; el compilador también podría obligarlo a escribir if (this == other) ...this.x...para lo que fuera a hacer. Su concepción de "conveniencia (incluso más si las variables privadas fueran públicas)" también pierde el sentido: la forma en que se define el Estándar es lo suficientemente restrictiva como para permitir una encapsulación adecuada , pero no innecesariamente inconveniente.
Tony Delroy
108

Los modificadores de acceso funcionan a nivel de clase y no a nivel de objeto .

Es decir, dos objetos de la misma clase pueden acceder a los datos privados del otro.

Por qué:

Principalmente debido a la eficiencia. Sería una sobrecarga de tiempo de ejecución no despreciable comprobar si this == othercada vez que accedeother.x lo que tendría que hacer, los modificadores de acceso funcionaron a nivel de objeto.

También es algo semánticamente lógico si lo piensas en términos de alcance: "¿Qué parte del código debo tener en cuenta al modificar una variable privada?" - Debe tener en cuenta el código de toda la clase, y esto es ortogonal a qué objetos existen en tiempo de ejecución.

Y es increíblemente conveniente al escribir constructores de copia y operadores de asignación.

aioobe
fuente
35

Puede acceder a miembros privados de una clase desde dentro de la clase, incluso a los de otra instancia.

Alexander Rafferty
fuente
10

Para comprender la respuesta, me gustaría recordarles algunos conceptos.

  1. No importa cuántos objetos cree, solo hay una copia de una función en la memoria para esa clase. Significa que las funciones se crean solo una vez. Sin embargo, las variables están separadas para cada instancia de la clase.
  2. this El puntero se pasa a cada función cuando se llama.

Ahora es por el this puntero, la función puede localizar variables de esa instancia en particular. no importa si es privado o público. se puede acceder dentro de esa función. Ahora si pasamos un puntero a otro objeto de la misma clase. usando este segundo puntero podremos acceder a miembros privados.

Espero que esto responda a su pregunta.

Ali Zaib
fuente
6

El constructor de copia es una función miembro de la clase y, como tal, tiene acceso a los miembros de datos de la clase, incluso aquellos declarados como 'privados'.

Bojan Komazec
fuente
3
Como alguien que conoce el idioma, entiendo lo que quieres decir. Sin embargo, si no supiera el idioma, habría pensado que me estaba repitiendo la pregunta.
San Jacinto