¿Por qué C ++ no permite la amistad heredada?

¿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 la línea de virtual friend class Foo; me desconcierta ¿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?

Edite para aclarar: estoy hablando de la siguiente situación, no de dónde los niños de A están expuestos a B o a ambos B y sus hijos. También me puedo imaginar, opcionalmente, otorgando acceso a anulaciones de funciones de amigos, etc.

 class A { int x; friend class B; }; class B { // OK as per friend declaration above. void foo(A& a, int n) { ax = n; } }; class D : public B { /* can't get in A w/o 'friend class D' declaration. */ }; 

Respuesta aceptada: como afirma Loki , el efecto puede simularse más o menos al hacer funciones de proxy protegidas en las clases base, por lo que no hay una necesidad estricta de 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 haya llegado el momento de comprar y leer The Design and Evolution of C ++ de Stroupstrup, que he visto suficientes personas aquí recomendar, para obtener una mejor comprensión de este tipo de preguntas …

    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 clases derivadas de Bar ?
    Realmente no. 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 lo tanto, si la representación interna de Foo se modifica, Bar también debe modificarse (porque la amistad se une estrechamente a Bar a Foo ). Si la amistad se hereda, todas las clases derivadas de Bar también se vincularán estrechamente con Foo y, por lo tanto, requerirán 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 podía 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 , solo haga que el método esté protegido en Bar . Entonces el hijo de Bar puede acceder a un Foo llamando a través de su clase principal.

    ¿Es esto 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) { ax = n; } }; class D : public B { public: foo(A& a, int n) { B::foo(a, n + 5); } }; 

    ¿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?”

    Una clase con amigos puede exponer a su amigo a través de funciones de acceso y luego otorgar acceso a través de ellos.

     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 la transitividad opcional. Por ejemplo, get_cash puede estar protected o puede hacer cumplir un protocolo de acceso limitado en tiempo de ejecución.

    Norma C ++, sección 11.4 / 8

    La amistad no es heredada 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 eso viola la encapsulación.

    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 el interior de una clase tiene el riesgo de afectar a los amigos de esta clase … ¿realmente 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 trae una regla muy simple:

    Cambiar las partes internas de una clase solo debería afectar la clase misma

    Por supuesto, probablemente afectará a sus amigos, pero hay dos casos aquí:

    • función de amigo libre: probablemente más una función de miembro de todos modos (creo que std::ostream& operator< <(...) aquí, que no es un miembro puramente por accidente de las reglas de lenguaje
    • clase de amigos? 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 Key simple le permite declarar a un amigo (en cierto modo) sin darle acceso a sus partes internas, aislándolo de los cambios. Además, permite que este amigo preste su clave a los fideicomisarios (como los niños) si es necesario.

    Una suposición: si una clase declara alguna otra clase / función como un 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?

    Una clase derivada solo puede heredar algo, que es ‘miembro’ de la base. Una statement de amigo no es miembro de la clase de amistad.

    $ 11.4 / 1- “… El nombre de un amigo no está en el scope de la clase, y no se llama al amigo con los operadores de acceso miembro (5.2.5) a menos que sea miembro de otra clase”.

    $ 11.4 – “Además, como la cláusula base de la clase de amigo no forma parte de sus declaraciones de miembros, la cláusula base de la clase de amigo no puede acceder a los nombres de los miembros privados y protegidos de la clase que otorga la amistad”.

    y además

    $ 10.3 / 7- “[Nota: el especificador virtual implica membresía, por lo que una función virtual no puede ser una función no miembro (7.1.2). Tampoco una función virtual puede ser un miembro estático, ya que una llamada de función virtual depende de un objeto específico para determinar qué función invocar. Una función virtual declarada en una clase puede ser declarada como amiga en otra clase.] ”

    Dado que el “amigo” no es miembro de la clase base en primer lugar, ¿cómo puede ser heredado por la clase derivada?

    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 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á.

    Amigo es bueno en herencia como interfaz de estilo para contenedor Pero para mí, como primer dicho, C ++ carece de la herencia propagable

     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 granularidad muy buena para controlar todo acceso desde cualquier lugar para cualquier cosa:

    amigo Thing puede convertirse en amigo Thing. * para otorgar acceso a todo niño de Thing

    Y más, el amigo [área nombrada] Cosa. * Para otorgar acceso de manera precisa está en la clase de Contenedor a través del área especial designada 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
    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(); } };