2010-08-24 21 views
73

¿Por qué la amistad no es al menos opcionalmente heredable en C++? Entiendo que la transitividad y la reflexividad están prohibidas por razones obvias (lo digo solo para evitar las respuestas a las preguntas frecuentes), pero la falta de algo en el sentido de virtual friend class Foo; me deja perplejo. ¿Alguien conoce los antecedentes históricos detrás de esta decisión? ¿La amistad realmente era solo un truco limitado que desde entonces ha encontrado su camino en algunos oscuros y respetables usos?¿Por qué C++ no permite la amistad heredada?

Editar una aclaración: que estoy hablando el siguiente escenario, no donde los niños de A están expuestos a cualquiera de B o B y sus hijos. También puedo imaginar concesión opcionalmente el acceso a las anulaciones de funciones friend, etc.

class A { 
    int x; 
    friend class B; 
}; 

class B { 
    // OK as per friend declaration above. 
    void foo(A& a, int n) { a.x = n; } 
}; 

class D : public B { /* can't get in A w/o 'friend class D' declaration. */ }; 

respuesta aceptada: como Loki states, el efecto puede ser simulado más o menos haciendo funciones de proxy protegidas en clases base friended, así que hay no es estricto necesita para otorgar amistad a una jerarquía de clase o método virtual. No me gusta la necesidad de proxies repetitivos (que la base de amigos se convierte en realidad), pero supongo que esto se consideró preferible a un mecanismo de lenguaje que probablemente sería mal utilizado la mayor parte del tiempo. Creo que es probable que sea hora de que compre y lea Stroupstrup's The Design and Evolution of C++, que he visto suficientes personas aquí, para obtener una mejor comprensión de este tipo de preguntas ...

Respuesta

69

Porque puedo escribir Foo y su amigo Bar (por lo tanto hay una relación de confianza).

¿Pero confío en las personas que escriben las clases que se derivan de Bar?
No realmente. Entonces ellos no deberían heredar la amistad.

Cualquier cambio en la representación interna de una clase requerirá una modificación a cualquier cosa que dependa de esa representación. Por lo tanto, todos los miembros de una clase y también todos los amigos de la clase requerirán modificaciones.

Por tanto, si la representación interna de Foo se modifica entonces Bar debe también ser modificado (porque la amistad se une fuertemente Bar a Foo). Si la amistad se hereda, toda clase derivada de Bar también estaría estrechamente vinculada a Foo y, por lo tanto, requerirá modificaciones si se modifica la representación interna de Foo. Pero no tengo conocimiento de los tipos derivados (ni debería I. Incluso pueden ser desarrollados por diferentes empresas, etc.). Por lo tanto, no podría cambiar Foo ya que al hacerlo introduciría cambios de última hora en la base de código (ya que no pude modificar toda la clase derivada de Bar).

Por lo tanto, si la amistad se hereda, inadvertidamente se introduce una restricción en la capacidad de modificar una clase. Esto es indeseable ya que básicamente hace inútil el concepto de una API pública.

Nota: Un hijo de Bar puede acceder a Foo usando Bar, simplemente haga que el método esté protegido en Bar.Entonces el hijo de Bar puede acceder a Foo llamando a través de su clase principal.

¿Esto es lo que quieres?

class A 
{ 
    int x; 
    friend class B; 
}; 

class B 
{ 
    protected: 
     // Now children of B can access foo 
     void foo(A& a, int n) { a.x = n; } 
}; 

class D : public B 
{ 
    public: 
     foo(A& a, int n) 
     { 
      B::foo(a, n + 5); 
     } 
}; 
+1

Bingo. Se trata de limitar el daño que puedes causar cambiando las partes internas de una clase. –

+0

Francamente, el caso en el que realmente estoy pensando es en el patrón de abogado-cliente, donde un intermediario actúa como una interfaz limitada para las clases externas al presentar los métodos de contenedor a la clase subyacente de acceso restringido. Decir que una interfaz está disponible para todos los niños de otras clases en lugar de una clase exacta sería mucho más útil que el sistema en la actualidad. – Jeff

+0

@Jeff: http://www.drdobbs.com/184402053 –

0

Supongo: si una clase declara alguna otra clase/función como amigo, es porque esa segunda entidad necesita acceso privilegiado a la primera. ¿De qué sirve otorgar a la segunda entidad acceso privilegiado a un número arbitrario de clases derivado de la primera?

+2

Si una clase Una quiso conceder la amistad a B y sus descendientes, que podría evitar la actualización de su interfaz para cada subclase agregada u obligando a B a escribir el texto repetitivo de paso a través, que es la mitad del punto de la amistad, en primer lugar, creo. – Jeff

+0

@Jeff: Ah, entonces malentendí el significado que pretendías. Supuse que querías decir que 'B' tendría acceso a todas las clases heredadas de' A' ... –

6

C Standard ++, sección 11,4/8

amistad ni se hereda ni transitiva.

Si la amistad se heredara, una clase que no estaba destinada a ser un amigo de repente tendría acceso a su clase interna y que viola la encapsulación.

+1

Diga Q "amigos" A, y B se deriva de A. Si B hereda la amistad de A, entonces porque B es un tipo de A, técnicamente ES una A que tiene acceso a las partes privadas de Q. Entonces esto no responde la pregunta con ninguna razón práctica. – mdenton8

0

Una clase derivada solo puede heredar algo, que es 'miembro' de la base. Una declaración de amigo es no miembro de la clase de amistad.

$ 11,4/1-" ... El nombre de un amigo es no en el ámbito de la clase, y el amigo no se llama a los operadores de acceso miembro de (5.2.5) a menos que sea miembro de otra clase ".

$ 11.4 - "Además, debido a la base cláusula de la clase amigo no es parte de sus declaraciones de miembros, la base cláusula de la clase amigo no puede acceder a las nombres de lo privado y protegido miembros de la clase que otorga la amistad ".

y más

$ 10,3/7- "[Nota: el especificador virtual implica la membresía, por lo que una función virtual no puede ser una función no miembro (7.1.2) Tampoco puede un virtual. función de ser un miembro estático, ya que un llamada de función virtual se basa en un objeto específico para determinar qué función a invocar. una función virtual declaró en una clase puede ser declarado un amigo en una otra clase. ]"

Puesto que el 'amigo' no es un miembro de la clase base, en primer lugar, ¿cómo puede ser heredada por la clase derivada?

+0

La amistad, aunque se da a través de declaraciones como miembros, no son realmente miembros sino notificaciones de otras clases que pueden ignorar las clasificaciones de visibilidad de los miembros 'reales'. Si bien las secciones de especificaciones que citan explican cómo funciona el lenguaje con respecto a estos matices y marcos de comportamiento en terminología autoconsistente, las cosas podrían haber sido elaboradas de forma diferente, y desafortunadamente nada de lo que está en el núcleo de la razón fundamental. – Jeff

7

Una clase friended puede exponer su amigo a través de funciones de acceso y después, conceda acceso a través de aquellos.

class stingy { 
    int pennies; 
    friend class hot_girl; 
}; 

class hot_girl { 
public: 
    stingy *bf; 

    int &get_cash(stingy &x = *bf) { return x.pennies; } 
}; 

class moocher { 
public: // moocher can access stingy's pennies despite not being a friend 
    int &get_cash(hot_girl &x) { return x.get_cash(); } 
}; 

Esto permite un control más fino que transitividad opcional. Por ejemplo, puede haber get_cashprotected o puede hacer cumplir un protocolo de acceso de tiempo de ejecución limitada.

+4

He leído mal centavos como pene ... – Hector

+0

@Hector: ¡Votación para la refactorización! ';)' –

2

Porque es innecesario.

El uso de la palabra clave friend es en sí mismo sospechoso. En términos de acoplamiento, es la peor relación (camino por delante de la herencia y la composición).

Cualquier cambio en las partes internas de una clase tiene un riesgo de afectar a los amigos de esta clase ... ¿de verdad quieres un número desconocido de amigos? Ni siquiera podría enumerarlos si los que heredan de ellos también pudieran ser amigos, y correría el riesgo de romper el código de sus clientes cada vez, seguramente esto no es deseable.

Admito libremente que para los proyectos de tareas/mascotas la dependencia a menudo es una consideración lejana. En proyectos de pequeño tamaño, no importa. Pero tan pronto como varias personas trabajen en el mismo proyecto y esto se convierta en las decenas de miles de líneas, debe limitar el impacto de los cambios.

Esto traerá una regla muy simple:

El cambio de los componentes internos de una clase sólo debe afectar a la propia clase

Por supuesto, es probable que afectan a sus amigos, pero hay dos casos aquí : función libre

  • amigo: probablemente más de una función miembro de todos modos (soy pensar std::ostream& operator<<(...) aquí, que no es un miembro puramente por accidente de las reglas del lenguaje
  • clase de amigo? no necesitas clases de amigos en clases reales.

Yo recomendaría el uso del método simple:

class Example; 

class ExampleKey { friend class Example; ExampleKey(); }; 

class Restricted 
{ 
public: 
    void forExampleOnly(int,int,ExampleKey const&); 
}; 

Este patrón simple Key permite declarar un amigo (de manera) sin llegar a lo que le da acceso a sus componentes internos, por lo tanto aislándolo de los cambios Además, permite que este amigo preste su clave a los fideicomisarios (como los niños) si es necesario.

29

¿Por qué la amistad no es al menos opcionalmente heredable en C++?

Creo que la respuesta a su primera pregunta es en esta pregunta: "¿Los amigos de tu padre tienen acceso a tus partes privadas?"

+16

Para ser justos, esa pregunta plantea inquietantes sobre tu padre. . . – iheanyi

+1

@iheanyi: Ese es el punto que está haciendo. \ o/ – wilx

+1

¿Cuál es el objetivo de esta respuesta? en el mejor de los casos, un comentario dudoso aunque posiblemente alegre – DeveloperChris

0

La función Friend en una clase asigna la propiedad extern a la función. es decir, externo significa que la función ha sido declarada y definida en algún lugar fuera de la clase.

Por lo tanto, significa que la función de amigo no es miembro de una clase. Entonces, la herencia solo le permite heredar las propiedades de una clase, no cosas externas. Y también si la herencia está permitida para las funciones de amigo, entonces una clase de tercero heredará.

0

amigo es bueno en la herencia como interfaz de estilo para el envase Pero para mí, como el primer ejemplo, C++ carecen de la herencia propagables

class Thing; 

//an interface for Thing container's 
struct IThing { 
    friend Thing; 
    protected: 
     int IThing_getData() = 0; 
}; 

//container for thing's 
struct MyContainer : public IThing { 
    protected: //here is reserved access to Thing 
     int IThing_getData() override {...} 
}; 

struct Thing { 
    void setYourContainer(IThing* aContainerOfThings) { 
     //access to unique function in protected area 
     aContainerOfThings->IThing_getData(); //authorized access 
    } 
}; 

struct ChildThing : public Thing { 
    void doTest() { 
     //here the lack of granularity, you cannot access to the container. 
     //to use the container, you must implement all 
     //function in the Thing class 
     aContainerOfThings->IThing_getData(); //forbidden access 
    } 
}; 

Para mí el problema de C++ es la falta de muy buena granularidad de control de todo el acceso desde cualquier lugar para cualquier cosa:

amigo cosa puede convertirse en amigo cosa * para conceder acceso a todos los niños de cosas

Y más, amigo [zona denominada] cosa..* para otorgar acceso para un preciso están en la clase Container a través de un área con nombre especial para el amigo.

Ok, detén el sueño. Pero ahora, sabes un uso interesante de amigo.

En otro orden, también puede encontrar interesante saber que todas las clases son amigables consigo mismas. En otras palabras, una instancia de clase puede llamar a todos los
miembros de otra instancia del mismo nombre sin restricción:

class Object { 
    private: 
     void test() {} 
    protected: 
     void callAnotherTest(Object* anotherObject) { 
      //private, but yes you can call test() from 
      //another object instance 
      anotherObject)->test(); 
     } 
}; 
Cuestiones relacionadas