Por que C ++ não permite amizade herdada?

93

Por que a amizade não é pelo menos opcionalmente herdada em C ++? Eu entendo que a transitividade e a reflexividade são proibidas por razões óbvias (digo isso apenas para evitar respostas simples de citação do FAQ), mas a falta de algo parecido com o que virtual friend class Foo;me intriga. Alguém conhece os antecedentes históricos por trás dessa decisão? A amizade era realmente apenas um hack limitado que desde então encontrou seu caminho para alguns usos obscuros e respeitáveis?

Edite para esclarecimento: estou falando sobre o seguinte cenário, não onde os filhos de A são expostos a B ou a B e seus filhos. Também posso imaginar a concessão de acesso opcional para substituições de funções de amigo, 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. */ };

Resposta aceita: como afirma Loki , o efeito pode ser simulado mais ou menos criando funções de proxy protegidas em classes base amigas, portanto, não há necessidade estrita de conceder amizade a uma classe ou hierarquia de método virtual. Não gosto da necessidade de proxies clichê (que a base de amigos efetivamente se torna), mas suponho que isso foi considerado preferível a um mecanismo de linguagem que provavelmente seria mal utilizado na maioria das vezes. Acho que provavelmente é hora de comprar e ler The Design and Evolution of C ++ , de Stroupstrup , que já vi muitas pessoas recomendarem, para ter uma visão melhor sobre esses tipos de perguntas ...

Jeff
fonte

Respostas:

93

Porque posso escrever Fooe seu amigo Bar(portanto, há uma relação de confiança).

Mas eu confio nas pessoas que escrevem classes derivadas de Bar?
Na verdade não. Portanto, eles não devem herdar amizade.

Qualquer mudança na representação interna de uma classe exigirá uma modificação em qualquer coisa que dependa dessa representação. Portanto, todos os membros de uma classe e também todos os amigos da classe exigirão modificações.

Portanto, se a representação interna de Fooé modificada, então Bartambém deve ser modificada (porque a amizade está fortemente ligada Bara Foo). Se a amizade fosse herdada, todas as classes derivadas de Bartambém seriam estreitamente vinculadas Fooe, portanto, exigiriam modificação se Fooa representação interna de fosse alterada. Mas não tenho conhecimento de tipos derivados (nem deveria. Eles podem até ser desenvolvidos por empresas diferentes, etc.). Portanto, eu seria incapaz de alterar, Foopois isso introduziria alterações significativas na base do código (já que não poderia modificar todas as classes derivadas de Bar).

Portanto, se a amizade foi herdada, você está inadvertidamente introduzindo uma restrição à capacidade de modificar uma classe. Isso é indesejável, pois você basicamente torna inútil o conceito de uma API pública.

Nota: Um filho de Barpode acessar Foousando Bar, basta tornar o método Barprotegido. Então, o filho de Barpode acessar a Foochamando por meio de sua classe pai.

É isso que voce quer?

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);
        }
};
Loki Astari
fonte
4
Bingo. É tudo uma questão de limitar o dano que você pode causar alterando o interior de uma classe.
j_random_hacker
Francamente, o caso em que estou realmente pensando é o padrão Attorney-Client, em que um intermediário atua como uma interface limitada para classes externas, apresentando métodos de wrapper para a classe de acesso restrito subjacente. Dizer que uma interface está disponível para todos os filhos de outras classes, em vez de uma classe exata, seria muito mais útil do que o sistema atual.
Jeff
@Jeff: Expor a representação interna a todos os filhos de uma classe fará com que o código se torne imutável (também quebra o encapsulamento, pois qualquer pessoa que quisesse acessar os membros internos só precisa herdar de Bar, mesmo que não seja realmente um Bar )
Martin York
@Martin: Certo, neste esquema a base de amigos pode ser usada para chegar à classe de amigos, o que pode ser uma simples violação de encapsulamento em muitos (se não na maioria) dos casos. No entanto, em situações em que a base amiga é uma classe abstrata, qualquer classe derivada seria coagida a implementar uma interface recíproca própria. Não tenho certeza se uma classe 'impostor' neste cenário seria considerada uma quebra de encapsulamento ou uma violação de um contrato de interface se não tentasse cumprir fielmente sua função reivindicada de maneira adequada.
Jeff
@Martin: Certo, esse é o efeito que eu quero e às vezes já uso, onde A é na verdade uma interface reciprocamente amiga de alguma classe de acesso restrito Z. A reclamação comum com o idioma advogado-cliente normal parece ser que a classe de interface A deve criar invólucros de chamadas padronizados para Z e, para estender o acesso às subclasses, o padrão de A deve ser essencialmente duplicado em todas as classes base, como B. Uma interface normalmente expressa qual funcionalidade um módulo deseja oferecer, não qual funcionalidade em outros ele quer usar a si mesmo.
Jeff
48

Por que a amizade não é pelo menos opcionalmente herdada em C ++?

Acho que a resposta à sua primeira pergunta está nesta pergunta: "Os amigos de seu pai têm acesso às suas partes íntimas?"

Wilx
fonte
36
Para ser justo, essa pergunta levanta questões perturbadoras sobre seu pai. . .
iheanyi
3
Qual é o ponto desta resposta? na melhor das hipóteses um comentário duvidoso, embora possivelmente leve
DeveloperChris
11

Uma classe com amizade pode expor seu amigo por meio de funções de acesso e, em seguida, conceder acesso por meio delas.

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(); }
};

Isso permite um controle mais preciso do que a transitividade opcional. Por exemplo, get_cashpode ser protectedou pode impor um protocolo de acesso limitado em tempo de execução.

Potatoswatter
fonte
@Hector: Votação para refatoração! ;)
Alexander Shukaev
7

Padrão C ++, seção 11.4 / 8

A amizade não é herdada nem transitiva.

Se a amizade fosse herdada, então uma classe que não era para ser um amigo de repente teria acesso aos internos da classe e isso viola o encapsulamento.

David
fonte
2
Digamos que Q "amigos" A e B seja derivado de A. Se B herda a amizade de A, então, como B é um tipo de A, tecnicamente é um A que tem acesso aos privates de Q. Portanto, isso não responde à pergunta com nenhuma razão prática.
mdenton8
2

Porque é simplesmente desnecessário.

O uso da friendpalavra-chave é em si suspeito. Em termos de acoplamento, é o pior relacionamento (muito à frente da herança e da composição).

Qualquer mudança no interior de uma classe corre o risco de impactar os amigos dessa classe ... você realmente quer um número desconhecido de amigos? Você nem mesmo seria capaz de listá-los se aqueles que herdam deles também pudessem ser amigos, e você correria o risco de quebrar o código de seus clientes todas as vezes, certamente isso não é desejável.

Admito francamente que, para tarefas de casa / projetos de estimação, a dependência costuma ser uma consideração distante. Em projetos de pequeno porte, isso não importa. Mas assim que várias pessoas trabalharem no mesmo projeto e isso crescer para as dezenas de milhares de linhas, você precisará limitar o impacto das mudanças.

Isso traz uma regra muito simples:

Mudar o interior de uma classe só deve afetar a própria classe

Claro, você provavelmente afetará seus amigos, mas há dois casos aqui:

  • função livre de amigos: provavelmente mais uma função de membro de qualquer maneira (acho que std::ostream& operator<<(...)aqui, que não é um membro puramente por acidente das regras de idioma
  • aula de amigo? você não precisa de aulas com amigos em aulas reais.

Eu recomendaria o uso do método simples:

class Example;

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

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

Este Keypadrão simples permite que você declare um amigo (de certa forma) sem realmente dar a ele acesso aos seus internos, isolando-o, assim, de alterações. Além disso, permite que este amigo empreste sua chave para administradores (como crianças), se necessário.

Matthieu M.
fonte
0

Uma suposição: se uma classe declara alguma outra classe / função como amiga, é porque essa segunda entidade precisa de acesso privilegiado à primeira. Qual a utilidade de conceder à segunda entidade acesso privilegiado a um número arbitrário de classes derivadas da primeira?

Oliver Charlesworth
fonte
2
Se uma classe A quisesse conceder amizade a B e seus descendentes, ela poderia evitar a atualização de sua interface para cada subclasse adicionada ou forçar B a escrever um boilerplate de passagem, que é a metade do ponto de amizade em primeiro lugar, eu acho.
Jeff
@Jeff: Ah, então eu entendi mal o que você queria dizer. Presumi que você quisesse dizer que Bteria acesso a todas as classes herdadas de A...
Oliver Charlesworth
0

Uma classe derivada pode herdar apenas algo, que é 'membro' da base. Uma declaração de amigo não é membro da classe de amizade.

$ 11.4 / 1- "... O nome de um amigo não está no escopo da classe, e o amigo não é chamado com os operadores de acesso de membro (5.2.5) a menos que seja membro de outra classe."

$ 11.4 - "Além disso, porque a cláusula-base da classe amiga não faz parte das declarações de seus membros, a cláusula-base da classe amiga não pode acessar os nomes dos membros privados e protegidos da classe que concede amizade."

e mais

$ 10.3 / 7- "[Nota: o especificador virtual implica associação, então uma função virtual não pode ser uma função não membro (7.1.2). Nem uma função virtual pode ser um membro estático, uma vez que uma chamada de função virtual depende de um objeto específico para determinar qual função invocar. Uma função virtual declarada em uma classe pode ser declarada amiga em outra classe.] "

Visto que o 'amigo' não é membro da classe base em primeiro lugar, como pode ser herdado pela classe derivada?

Chubsdad
fonte
Amizade, embora dada por meio de declarações como membros, não são realmente membros, mas notificações de que outras classes podem essencialmente ignorar as classificações de visibilidade em membros 'reais'. Embora as seções de especificações que você cita expliquem como a linguagem opera em relação a essas nuances e molda o comportamento em uma terminologia autoconsistente, as coisas poderiam ter sido criadas de maneira diferente e, infelizmente, nada acima atinge o cerne da lógica.
Jeff
0

A função de amigo em uma classe atribui a propriedade extern à função. ou seja, extern significa que a função foi declarada e definida em algum lugar fora da classe.

Portanto, significa que a função de amigo não é membro de uma classe. Portanto, a herança só permite que você herde as propriedades de uma classe, não coisas externas. E também se a herança for permitida para funções de amigo, então uma herança de classe de terceiros.

Robert
fonte
0

Friend é bom em herança como interface de estilo para contêiner Mas para mim, como digo primeiro, C ++ não possui a herança propagável

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 mim, o problema do C ++ é a falta de granularidade muito boa para controlar todo o acesso de qualquer lugar para qualquer coisa:

amigo Thing pode se tornar amigo Thing. * para conceder acesso a todos os filhos de Thing

E mais, amigo [área nomeada] Thing. * Para conceder acesso para uma área precisa na classe Container via área com nome especial para o amigo.

Ok, pare o sonho. Mas agora, você conhece um uso interessante de amigo.

Em outra ordem, você também pode achar interessante saber que todas as classes são amigáveis ​​consigo mesmas. Em outras palavras, uma instância de classe pode chamar todos os
membros de outra instância de mesmo nome sem restrição:

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

Lógica simples: 'Eu tenho uma amiga Jane. Só porque nos tornamos amigos ontem, não faz com que todos os amigos dela sejam meus.

Ainda preciso aprovar essas amizades individuais, e o nível de confiança seria adequado.

Robert Hamm
fonte