2012-07-24 29 views
33

¿Por qué esta compilación:acceder a un miembro protegido de una clase base en otra subclase

class FooBase 
{ 
protected: 
    void fooBase(void); 
}; 

class Foo : public FooBase 
{ 
public: 
    void foo(Foo& fooBar) 
    { 
     fooBar.fooBase(); 
    } 
}; 

pero esto no lo hace?

class FooBase 
{ 
protected: 
    void fooBase(void); 
}; 

class Foo : public FooBase 
{ 
public: 
    void foo(FooBase& fooBar) 
    { 
     fooBar.fooBase(); 
    } 
}; 

Por un lado subvenciones C++ acceso a los miembros privados/protegidas para todas las instancias de esa clase, pero por otro lado no permitir el acceso a los miembros protegidos de una clase base para todas las instancias de una subclase. Esto me parece bastante inconsistente.

He probado la compilación con VC++ y con ideone.com y ambos compilan el primer fragmento de código, pero no el segundo.

+0

@iammilind ¿Estás seguro de que estás cerrando la pregunta correcta? ¿O debería ser al revés? – Eiko

+0

@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

Respuesta

28

Cuando foo recibe una referencia FooBase, el compilador no sabe si el argumento es un descendiente de Foo, por lo que tiene que asumir que no lo es. Foo tiene acceso a miembros protegidos heredados de otros objetos, no todas las demás clases de hermanos.

consideran este código:

class FooSibling: public FooBase { }; 

FooSibling sib; 
Foo f; 
f.foo(sib); // calls sib.fooBase()!? 

Si Foo::foo puede llamar a los miembros protegidos de arbitrarias FooBase descendientes, entonces se puede llamar al método protegido de FooSibling, que no tiene relación directa con Foo. No es así como se supone que el acceso protegido funciona.

Si Foo necesita tener acceso a los miembros protegidos de todas las FooBase objetos, no sólo los que también son conocidos por ser Foo descendientes, entonces Foo tiene que ser un amigo del FooBase:

class FooBase 
{ 
protected: 
    void fooBase(void); 
    friend class Foo; 
}; 
+0

Ah, con su código de ejemplo el motivo, por qué no está permitido, es bastante obvio. Gracias. – Kaiserludi

+0

No entiendo tu ejemplo. Si fooBase no es virtual, lo que obtienes es FooBase :: fooBase, que es lo que indicas de todos modos. Si fooBase es virtual, en realidad está llamando a FooSibling :: fooBase, pero también es la razón por la que está usando funciones virtuales: ¿para poder adaptar las funciones al objeto real? No veo cuando este comportamiento es un problema. –

+0

Virtual es irrelevante aquí, @Vincent. El estado virtual de un miembro no afecta quién puede usar su nombre. La * visibilidad * del miembro es lo que determina quién puede usar su nombre. 'Foo' puede ver miembros protegidos de otros objetos' Foo' conocidos. No puede ver miembros protegidos de ningún otro objeto, ni siquiera los relacionados con sus clases ancestrales, porque esos antepasados ​​no son necesarios * conocidos * para ser 'Foo'. La virtualidad y la visibilidad son conceptos ortogonales en C++ (pero no necesariamente en otros idiomas). –

3

En ambos ejemplos Foo hereda un método protegido fooBase. Sin embargo, en el primer ejemplo, intenta acceder al método protegido de la misma clase (Foo::foo llama al Foo::fooBase), mientras que en el segundo ejemplo intenta acceder a un método protegido de otra clase que no está declarada como clase amiga (Foo::foo intenta para llamar al FooBase::fooBase, que falla, el último está protegido).

1

En el primer ejemplo se pasa un objeto de tipo Foo, que obviamente hereda el método fooBase() y así puede llamarlo. En el segundo ejemplo, intenta llamar a una función protegida, simplemente así, independientemente del contexto en el que no pueda llamar a una función protegida desde una instancia de clase donde se declare así. En el primer ejemplo, usted hereda el método protegido fooBase, por lo que tiene derecho a llamarlo dentro del contexto Foo

1

Tiendo a ver las cosas en términos de conceptos y mensajes. Si su método FooBase se llamaba realmente "SendMessage" y Foo era "EnglishSpeakingPerson" y FooBase era SpeakingPerson, su protegida declaración tiene la intención de restringir SendMessage entre EnglishSpeakingPersons (y subclases, por ejemplo: AmericanEnglishSpeakingPerson, AustralianEnglishSpeakingPerson). Otro tipo FrenchSpeakingPerson derivado de SpeakingPerson no podría recibir un SendMessage, a menos que declarara FrenchSpeakingPerson como amigo, donde 'friend' significaba que FrenchSpeakingPerson tiene una capacidad especial para recibir SendMessage de EnglishSpeakingPerson (es decir, puede entender inglés).

9

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 ?!?!?! 
    } 
} 
19

El C++ FAQ resume muy bien este problema:

[usted] se les permite escoger sus propios bolsillos, pero no están autorizados a recoger los bolsillos de su padre ni bolsillos de tu hermano.

+8

puedes elegir tu propio bolsillo y el de tu hijo –

0

Además de hobo's answer, puede buscar una solución.

Si desea que las subclases quieran llamar al método fooBase puede hacerlo static. los métodos protegidos estáticos son accesibles por subclases con todos los argumentos.

Cuestiones relacionadas