El punto clave es que protected
le otorga acceso a su propia copia del miembro, no a aquellos miembros en cualquier otro objeto. Esta es una idea errónea común, ya que generalmente generalizamos y declaramos que protected
otorga acceso al miembro al tipo derivado (sin indicarlo explícitamente solo en sus propias bases ...)
Ahora, eso es por una razón y, en general, no debe acceder al miembro en una rama diferente de la jerarquía, ya que puede romper las invariantes de las que dependen otros objetos. Considere un tipo que realiza un cálculo costoso en algún miembro grande de datos (protegida) y dos tipos derivados que almacena en caché el resultado siguiente diferentes estrategias:
class base {
protected:
LargeData data;
// ...
public:
virtual int result() const; // expensive calculation
virtual void modify(); // modifies data
};
class cache_on_read : base {
private:
mutable bool cached;
mutable int cache_value;
// ...
virtual int result() const {
if (cached) return cache_value;
cache_value = base::result();
cached = true;
}
virtual void modify() {
cached = false;
base::modify();
}
};
class cache_on_write : base {
int result_value;
virtual int result() const {
return result_value;
}
virtual void modify() {
base::modify();
result_value = base::result();
}
};
El tipo cache_on_read
Captura modificaciones a los datos y marca el resultado como no válido, para que el siguiente lea del valor recalculado. Este es un buen enfoque si el número de escrituras es relativamente alto, ya que solo realizamos el cálculo a pedido (es decir, las modificaciones múltiples no desencadenarán recálculos). El cache_on_write
precalcula el resultado por adelantado, lo que podría ser una buena estrategia si el número de escrituras es pequeño y desea costos determinísticos para la lectura (piense en baja latencia en las lecturas).
Ahora, volviendo al problema original. Ambas estrategias de caché mantienen un conjunto más estricto de invariantes que la base. En el primer caso, la invariante adicional es que cached
es true
solo si data
no se ha modificado después de la última lectura. En el segundo caso, la invariante adicional es que result_value
es el valor de la operación en todo momento.
Si un tercer tipo derivado tomó una referencia a un base
y se accede data
para escribir (si protected
le permitió), entonces sería romper con los invariantes de los tipos derivados.
Dicho esto, la especificación del lenguaje es roto (opinión personal) ya que deja una puerta trasera para lograr ese resultado particular. En particular, si crea un puntero al miembro de un miembro desde una base en un tipo derivado, el acceso se marca en derived
, pero el puntero devuelto es un puntero al miembro base
, que puede aplicarse a cualquier objetobase
:
class base {
protected:
int x;
};
struct derived : base {
static void modify(base& b) {
// b.x = 5; // error!
b.*(&derived::x) = 5; // allowed ?!?!?!
}
}
@iammilind ¿Estás seguro de que estás cerrando la pregunta correcta? ¿O debería ser al revés? – Eiko
@Eiko originalmente cerré el otro con esto primero. Luego encontré que la respuesta presente allí era más elaborada y citada con el estándar. Por lo tanto reabrió eso y cerró esto. – iammilind