Pregunta ¿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 (digo esto solo para evitar las respuestas a las preguntas más frecuentes), pero la falta de algo a lo largo de las líneas 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?

Editar para aclarar: Estoy hablando de la siguiente situación, no donde 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) { a.x = n; }
};

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

Respuesta aceptada: como Estados Loki, el efecto se puede simular más o menos haciendo funciones de proxy protegidas en clases de base, por lo que no hay un estricto necesitar 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 hora de que compre y lea Stroupstrup El diseño y la evolución de C ++, que he visto a suficientes personas aquí recomendar, para obtener una mejor comprensión de este tipo de preguntas ...


75


origen


Respuestas:


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 entonces Bar también debe ser modificado (porque la amistad se une fuertemente Bar a Foo) Si la amistad fue heredada, toda clase derivada de Bar también estaría estrechamente unido a Foo y por lo tanto requieren modificación si Foose cambia la representación interna 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 del código (ya que no podría modificar todas las clases derivadas 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 Foo mediante el uso Bar, solo crea el método en Bar protegido. Entonces el niño de Bar puede acceder a 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) { a.x = n; }
};

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

73



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

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


33



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 tal vez protected o puede hacer cumplir un protocolo de acceso limitado en tiempo de ejecución.


7



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.


7



Porque es innecesario.

El uso de la friend la palabra clave es en sí misma sospechosa. 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 de una función miembro de todos modos (estoy pensar std::ostream& operator<<(...) aquí, que no es miembro simplemente por accidente de las reglas del idioma
  • 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 simple Key El patrón le permite declarar a un amigo (en cierta forma) 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.


2



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?


0



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

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

$ 11.4 - "Además, porque la cláusula base   de la clase de amigo no es parte de su   declaraciones de miembros, la cláusula de base   de la clase de amigo no puede acceder al   nombres de los privados y protegidos   miembros de la clase que otorga   amistad."

y además

$ 10.3 / 7- "[Nota: el especificador virtual   implica membresía, por lo que un virtual   la función no puede ser un no miembro (7.1.2)   función. Tampoco puede una función virtual   ser un miembro estático, ya que un virtual   función de llamada se basa en un específico   objeto para determinar qué función   invocar. Una función virtual declarada   en una clase puede ser declarado amigo   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?


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 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 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 el 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, amigo [área nombrada] Cosa. * Para otorgar acceso para una precisa están en la clase Container 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(); 
         }
};

0