Eu tenho lido as Perguntas frequentes sobre C ++ e fiquei curioso sobre a friend
declaração. Eu pessoalmente nunca o usei, mas estou interessado em explorar o idioma.
Qual é um bom exemplo de uso friend
?
Lendo um pouco mais as perguntas frequentes, gosto da ideia do <<
>>
operador sobrecarregar e adicionar como amigo dessas classes. No entanto, não tenho certeza de como isso não quebra o encapsulamento. Quando essas exceções podem permanecer dentro do rigor que é OOP?
c++
oop
encapsulation
friend
Peter Mortensen
fonte
fonte
Respostas:
Em primeiro lugar (IMO) não ouça as pessoas que dizem que
friend
não é útil. É útil. Em muitas situações, você terá objetos com dados ou funcionalidades que não pretendem estar disponíveis ao público. Isto é particularmente verdade em grandes bases de código com muitos autores que podem estar familiarizados superficialmente com diferentes áreas.Existem alternativas para o especificador de amigo, mas muitas vezes são complicadas (classes concretas no nível cpp / typedefs mascarados) ou não são infalíveis (comentários ou convenções de nomes de funções).
Para a resposta;
O
friend
especificador permite que a classe designada acesse dados ou funcionalidades protegidas dentro da classe que faz a declaração de amigo. Por exemplo, no código abaixo, qualquer pessoa pode pedir o nome de uma criança, mas apenas a mãe e a criança podem mudar o nome.Você pode levar esse exemplo mais adiante, considerando uma classe mais complexa, como uma Janela. Provavelmente, uma janela terá muitos elementos de função / dados que não devem ser acessíveis ao público, mas SÃO necessários para uma classe relacionada, como um WindowManager.
fonte
friend
aprimora o encapsulamento.friend
concede acesso seletivo aos membros, assim comoprotected
faz. Qualquer controle refinado é melhor do que conceder acesso público. Outros idiomas também definem mecanismos de acesso seletivo, considere C # 'sinternal
. A maioria das críticas negativas sobre o uso defriend
está relacionada a um acoplamento mais rígido, que geralmente é visto como uma coisa ruim. No entanto, em alguns casos, o acoplamento mais apertado é exatamente o que você deseja efriend
fornece esse poder.friend
é, em vez de fornecer um exemplo motivador . O exemplo Window / WindowManager é melhor que o exemplo mostrado, mas é muito vago. Essa resposta também não aborda a parte de encapsulamento da pergunta.No trabalho, usamos amigos para testar código extensivamente. Isso significa que podemos fornecer o encapsulamento adequado e a ocultação de informações para o código principal do aplicativo. Mas também podemos ter um código de teste separado que usa amigos para inspecionar o estado interno e os dados para teste.
Basta dizer que eu não usaria a palavra-chave friend como um componente essencial do seu design.
fonte
A
friend
palavra-chave tem vários bons usos. Aqui estão os dois usos imediatamente visíveis para mim:Definição de Amigo
A definição de amigo permite definir uma função no escopo da classe, mas a função não será definida como uma função membro, mas como uma função livre do espaço para nome que o acompanha e não será visível normalmente, exceto pela pesquisa dependente de argumento. Isso o torna especialmente útil para a sobrecarga do operador:
Classe base privada de CRTP
Às vezes, você encontra a necessidade de uma política precisar acessar a classe derivada:
Você encontrará um exemplo não artificial para isso nesta resposta. Outro código usando isso está nesta resposta. A base CRTP lança esse ponteiro para poder acessar campos de dados da classe derivada usando ponteiros de membro de dados.
fonte
P<C>
emtemplate<template<typename> class P> class C : P<C> {};
afirmar "Uso de modelo de classe C requer argumentos de modelo". Você já teve os mesmos problemas ou talvez tenha uma solução?FlexibleClass
insideFlexibleClass
deve implicitamente se referir ao seu próprio tipo.@roo : O encapsulamento não é quebrado aqui porque a própria classe determina quem pode acessar seus membros privados. O encapsulamento só seria quebrado se isso pudesse ser causado de fora da classe, por exemplo, se você
operator <<
proclamasse "eu sou amigo da classefoo
".friend
substitui o uso depublic
, não o uso deprivate
!Na verdade, o FAQ do C ++ já responde a isso .
fonte
friend
não é exceção. A única observação real aqui é que o C ++ garante o encapsulamento apenas no tempo de compilação. E você não precisa de mais palavras para dizer isso. O resto são besteiras. Então, em resumo: não vale a pena mencionar esta seção do FQA.O exemplo canônico é sobrecarregar o operador <<. Outro uso comum é permitir que um auxiliar ou uma classe de administrador acesse seus internos.
Aqui estão algumas diretrizes que ouvi sobre amigos em C ++. O último é particularmente memorável.
fonte
friend
eu acho.Como isso quebraria o encapsulamento?
Você quebra o encapsulamento ao permitir acesso irrestrito a um membro de dados. Considere as seguintes classes:
c1
é , obviamente, não encapsulado. Qualquer um pode ler e modificarx
nele. Não temos como impor nenhum tipo de controle de acesso.c2
é obviamente encapsulado. Não há acesso público ax
. Tudo o que você pode fazer é chamar afoo
função, que executa alguma operação significativa na classe .c3
? Isso é menos encapsulado? Permite acesso irrestrito ax
? Permite o acesso a funções desconhecidas?Não. Permite precisamente uma função para acessar os membros privados da classe. Assim como
c2
fez. E assimc2
, a única função que tem acesso não é "alguma função aleatória e desconhecida", mas "a função listada na definição de classe". Assimc2
, podemos ver, apenas olhando as definições de classe, uma lista completa de quem tem acesso.Então, como exatamente isso é menos encapsulado? A mesma quantidade de código tem acesso aos membros privados da classe. E todos os que têm acesso são listados na definição de classe.
friend
não quebra o encapsulamento. Isso faz com que alguns programadores de Java se sintam desconfortáveis, porque quando dizem "OOP", na verdade querem dizer "Java". Quando eles dizem "Encapsulamento", não significam "membros privados devem ser protegidos contra acessos arbitrários", mas "uma classe Java onde as únicas funções capazes de acessar membros privados são membros da classe", mesmo que isso seja um absurdo completo para várias razões .Primeiro, como já mostrado, é muito restritivo. Não há razão para que os métodos de amigos não devam fazer o mesmo.
Segundo, não é suficientemente restritivo . Considere uma quarta classe:
Isso, de acordo com a mentalidade Java acima mencionada, é perfeitamente encapsulado. E, no entanto, permite que qualquer pessoa leia e modifique x . Como isso faz sentido? (dica: não)
Conclusão: o encapsulamento é capaz de controlar quais funções podem acessar membros privados. É não sobre precisamente onde as definições destas funções estão localizados.
fonte
Outra versão comum do exemplo de Andrew, o temido código-dístico
Em vez de se preocupar se as duas linhas são sempre feitas juntas e em ordem consistente, você pode tornar os métodos privados e ter uma função de amigo para reforçar a consistência:
Em outras palavras, você pode manter as interfaces públicas menores e aplicar invariantes que atravessam classes e objetos nas funções de amigo.
fonte
addChild
função de membro também defina o pai?setParent
um amigo, pois você não deseja permitir que os clientes alterem o pai, pois você o gerenciará na categoriaaddChild
/removeChild
de funções.Você controla os direitos de acesso de membros e funções usando o direito Privado / Protegido / Público? então, assumindo que a ideia de cada um desses três níveis é clara, deve ficar claro que estamos perdendo alguma coisa ...
A declaração de um membro / função como protegida, por exemplo, é bastante genérica. Você está dizendo que esta função está fora do alcance de todos (exceto um filho herdado, é claro). Mas e as exceções? todo sistema de segurança permite que você tenha algum tipo de 'lista branca', certo?
Tão amigo permite que você tenha a flexibilidade de ter um isolamento sólido de objetos, mas permite que uma "brecha" seja criada para as coisas que você acha que são justificadas.
Eu acho que as pessoas dizem que não é necessário, porque sempre existe um design que funcionará sem ele. Eu acho que é semelhante à discussão de variáveis globais: você nunca deve usá-las, sempre há uma maneira de ficar sem elas ... mas, na realidade, você vê casos em que essa acaba sendo a maneira (quase) mais elegante. .. Eu acho que esse é o mesmo caso com os amigos.
bem, essa não é exatamente a maneira de encarar. A idéia é controlar a OMS pode acessar o que, tendo ou não uma função de configuração, tem pouco a ver com isso.
fonte
friend
uma brecha? Ele permite que os métodos listados na classe acessem seus membros privados. Ainda não permite que códigos arbitrários os acessem. Como tal, não é diferente de uma função de membro pública.Encontrei um lugar útil para usar o acesso de amigos: o mais pequeno de funções privadas.
fonte
O amigo é útil quando você está construindo um contêiner e deseja implementar um iterador para essa classe.
fonte
Tivemos um problema interessante em uma empresa em que trabalhei anteriormente, onde usamos amigos para afetar decentemente. Trabalhei em nosso departamento de estrutura, criamos um sistema básico de nível de mecanismo em nosso sistema operacional personalizado. Internamente, tínhamos uma estrutura de classes:
Todas essas classes fizeram parte da estrutura e mantidas por nossa equipe. Os jogos produzidos pela empresa foram construídos sobre essa estrutura, derivados de uma das crianças dos Jogos. O problema era que o Game tinha interfaces para várias coisas às quais o SinglePlayer e o TwoPlayer precisavam acessar, mas que não queríamos expor fora das classes de estrutura. A solução foi tornar essas interfaces privadas e permitir o acesso do TwoPlayer e SinglePlayer a elas por meio de amizade.
Sinceramente, todo esse problema poderia ter sido resolvido com uma melhor implementação de nosso sistema, mas estávamos presos ao que tínhamos.
fonte
A resposta curta seria: use friend quando realmente melhorar encapsulamento. Melhorar a legibilidade e a usabilidade (operadores << e >> são o exemplo canônico) também é um bom motivo.
Quanto aos exemplos de aprimoramento do encapsulamento, as classes projetadas especificamente para trabalhar com os internos de outras classes (as classes de teste vêm à mente) são boas candidatas.
fonte
<<
e>>
geralmente são amigos, em vez de membros, porque torná-los membros os tornaria difíceis de usar. Claro, estou falando sobre o caso em que esses operadores precisam acessar dados privados; caso contrário, a amizade é inútil.operator<<
eoperator>>
membros da classe de valor, em vez de não membros, ou membros dei|ostream
, não forneceriam a sintaxe desejada, e eu não estou sugerindo isso. " Estou falando do caso em que esses operadores precisam acessar dados privados ". Não vejo bem por que os operadores de entrada / saída precisariam acessar membros privados.O criador do C ++ diz que não está violando nenhum princípio de encapsulamento, e vou citá-lo:
É mais do que claro ...
fonte
Outro uso: friend (+ herança virtual) pode ser usado para evitar derivar de uma classe (também conhecida como "tornar uma classe subivivível") => 1 , 2
De 2 :
fonte
Para fazer TDD muitas vezes, usei a palavra-chave 'friend' em C ++.
Um amigo pode saber tudo sobre mim?
Atualizado: Encontrei esta resposta valiosa sobre a palavra-chave "friend" no site Bjarne Stroustrup .
fonte
Você precisa ter muito cuidado com quando / onde usa a
friend
palavra - chave e, como você, eu a uso muito raramente. Abaixo estão algumas notas sobre o usofriend
e as alternativas.Digamos que você queira comparar dois objetos para ver se são iguais. Você pode:
O problema com a primeira opção é que isso pode ser MUITO acessador, que é (ligeiramente) mais lento que o acesso variável direto, mais difícil de ler e complicado. O problema com a segunda abordagem é que você quebra completamente o encapsulamento.
O que seria bom, é se pudéssemos definir uma função externa que ainda pudesse ter acesso aos membros privados de uma classe. Podemos fazer isso com a
friend
palavra-chave:O método
equal(Beer, Beer)
agora tem acesso direto aoa
eb
's membros privados (que pode serchar *brand
,float percentAlcohol
etc. Este é um exemplo bastante artificial, você preferiria se aplicamfriend
a uma sobrecarga== operator
, mas nós vamos chegar a isso.Algumas coisas a serem observadas:
friend
NÃO é uma função membro da classepublic
!)Eu realmente uso apenas
friends
quando é muito mais difícil fazê-lo de outra maneira. Como outro exemplo, muitos de matemática vetor funções são muitas vezes criados comofriends
devido à interoperabilidade dosMat2x2
,Mat3x3
,Mat4x4
,Vec2
,Vec3
,Vec4
, etc. E é muito mais fácil de ser amigos, em vez de ter que usar acessores em todos os lugares. Conforme apontado,friend
geralmente é útil quando aplicado ao<<
(realmente útil para depuração)>>
e talvez ao==
operador, mas também pode ser usado para algo como isto:Como já disse, não uso com
friend
muita frequência, mas de vez em quando é exatamente o que você precisa. Espero que isto ajude!fonte
No que diz respeito ao operador << e operador >>, não há boas razões para tornar esses operadores amigos. É verdade que eles não devem ser funções-membro, mas também não precisam ser amigos.
A melhor coisa a fazer é criar funções públicas de impressão (ostream &) e leitura (istream &). Em seguida, escreva o operador << e o operador >> em termos dessas funções. Isso oferece o benefício adicional de permitir que você virtualize essas funções, o que fornece serialização virtual.
fonte
Só estou usando a palavra-chave friend para unittest funções protegidas. Alguns dirão que você não deve testar a funcionalidade protegida. No entanto, acho essa ferramenta muito útil ao adicionar novas funcionalidades.
No entanto, eu não uso a palavra-chave diretamente nas declarações de classe; em vez disso, uso um elegante corte de modelo para obter isso:
Isso me permite fazer o seguinte:
Trabalha em GCC e MSVC pelo menos.
fonte
No C ++, a palavra-chave "friend" é útil na sobrecarga do operador e no Making Bridge.
Agora, a primeira opção não é boa porque, se precisarmos sobrecarregar novamente esse operador para alguma classe diferente, teremos que fazer novamente alterações na classe "ostream".1.) Palavra-chave Friend na sobrecarga do operador:
Exemplo de sobrecarga do operador é: Digamos que tenhamos uma classe "Point" que possui duas variáveis flutuantes
"x" (para coordenadas x) e "y" (coordenadas y). Agora temos que sobrecarregar
"<<"
(operador de extração) de forma que, se chamarmos"cout << pointobj"
, ela imprimirá as coordenadas xey (onde pointobj é um objeto da classe Point). Para fazer isso, temos duas opções:É por isso que a segunda é a melhor opção. Agora o compilador pode chamar a
"operator <<()"
função:Beacause implementamos sobrecarga na classe Point. Então, para chamar essa função sem um objeto, precisamos adicionar uma
"friend"
palavra-chave, porque podemos chamar uma função de amigo sem um objeto. Agora a declaração da função será As:"friend ostream &operator<<(ostream &cout, Point &pointobj);"
2.) Palavra-chave Friend ao fazer bridge:
Suponha que tenhamos que criar uma função na qual temos que acessar membro privado de duas ou mais classes (geralmente denominadas "bridge"). Como fazer isso:
Para acessar um membro privado de uma classe, ele deve ser membro dessa classe. Agora, para acessar um membro privado de outra classe, toda classe deve declarar essa função como uma função de amigo. Por exemplo: Suponha que haja duas classes A e B. Uma função
"funcBridge()"
deseja acessar um membro privado de ambas as classes. Então ambas as classes devem declarar"funcBridge()"
como:friend return_type funcBridge(A &a_obj, B & b_obj);
Acho que isso ajudaria a entender a palavra-chave friend.
fonte
Como a referência para declaração de amigo diz:
Assim, como lembrete, existem erros técnicos em algumas das respostas que dizem que
friend
só podem visitar membros protegidos .fonte
O exemplo de árvore é um exemplo muito bom: ter um objeto implementado em algumas classes diferentes sem ter um relacionamento de herança.
Talvez você também precise ter um construtor protegido e forçar as pessoas a usar sua fábrica de "amigos".
... Ok, bem, francamente, você pode viver sem ele.
fonte
Não, é apenas uma amizade unidirecional: `(
fonte
Uma instância específica em que uso
friend
é ao criar classes Singleton . Afriend
palavra-chave permite criar uma função de acessador, que é mais concisa do que sempre ter um método "GetInstance ()" na classe.fonte
friend
se não precisar de uma "justificação" particular, ao adicionar uma função membro não.Funções e classes de amigos fornecem acesso direto a membros privados e protegidos da classe para evitar a quebra do encapsulamento no caso geral. A maioria dos usos é com ostream: gostaríamos de poder digitar:
No entanto, isso pode exigir acesso aos dados privados do Point, portanto, definimos o operador sobrecarregado
Existem implicações óbvias no encapsulamento, no entanto. Primeiro, agora a classe ou função de amigo tem acesso total a TODOS os membros da classe, mesmo aqueles que não pertencem a suas necessidades. Segundo, as implementações da classe e do amigo agora estão enredadas ao ponto em que uma mudança interna na classe pode prejudicar o amigo.
Se você vê o amigo como uma extensão da classe, isso não é um problema, logicamente falando. Mas, nesse caso, por que era necessário desenterrar o amigo em primeiro lugar.
Para conseguir a mesma coisa que os 'amigos' pretendem alcançar, mas sem quebrar o encapsulamento, pode-se fazer isso:
O encapsulamento não está quebrado, a classe B não tem acesso à implementação interna em A, mas o resultado é o mesmo como se tivéssemos declarado B amigo de A. O compilador otimizará as chamadas de função, resultando no mesmo instruções como acesso direto.
Eu acho que usar 'amigo' é simplesmente um atalho com benefício discutível, mas custo definido.
fonte
Pode não ser uma situação real de caso de uso, mas pode ajudar a ilustrar o uso de um amigo entre as classes.
The ClubHouse
A classe dos membros
Facilidades
Se você olhar para o relacionamento dessas classes aqui; o ClubHouse possui uma variedade de tipos diferentes de associação e acesso à associação. Os membros são todos derivados de uma classe super ou base, pois todos compartilham um ID e um tipo enumerado que são comuns e classes externas podem acessar seus IDs e tipos por meio de funções de acesso encontradas na classe base.
No entanto, através desse tipo de hierarquia dos membros e de suas classes derivadas e seu relacionamento com a classe ClubHouse, a única classe derivada que possui "privilégios especiais" é a classe VIPMember. A classe base e as outras 2 classes derivadas não podem acessar o método joinVIPEvent () do ClubHouse, mas a classe Membro VIP tem esse privilégio como se tivesse acesso completo a esse evento.
Portanto, com o VIPMember e o ClubHouse, é uma via de acesso bidirecional, onde as outras classes de membros são limitadas.
fonte
Ao implementar algoritmos de árvore para classe, o código de estrutura que o professor nos deu teve a classe de árvore como amiga da classe de nó.
Realmente não adianta nada, além de permitir que você acesse uma variável de membro sem usar uma função de configuração.
fonte
Você pode usar amizade quando classes diferentes (que não herdam uma da outra) estão usando membros particulares ou protegidos da outra classe.
de http://www.cplusplus.com/doc/tutorial/inheritance/ .
Você pode ver este exemplo em que o método não membro acessa os membros privados de uma classe. Este método deve ser declarado nesta mesma classe como um amigo da classe.
fonte
Provavelmente, perdi algo nas respostas acima, mas outro conceito importante no encapsulamento é ocultar a implementação. Reduzir o acesso a membros de dados privados (os detalhes de implementação de uma classe) permite uma modificação muito mais fácil do código posteriormente. Se um amigo acessar diretamente os dados privados, qualquer alteração nos campos de dados de implementação (dados privados), quebre o código que acessa esses dados. O uso de métodos de acesso elimina isso principalmente. Bastante importante eu pensaria.
fonte
Você pode aderir aos princípios mais rigorosos e mais pura OOP e garantir que nenhum membro de dados para qualquer classe até ter acessores para que todos os objetos devem ser os únicos que podem saber sobre os seus dados com a única maneira de agir sobre eles é através indiretos mensagens , ou seja, métodos.
Mas mesmo o C # tem uma palavra-chave de visibilidade interna e o Java tem sua acessibilidade no nível do pacote padrão para algumas coisas. O C ++ chega realmente mais perto do ideal de POO, minimizando o comprometimento da visibilidade em uma classe, especificando exatamente qual outra classe e apenas outras classes poderiam ver nela.
Eu realmente não uso C ++, mas se o C # tivesse amigos, eu o faria em vez do modificador interno assembly-global , que eu realmente uso muito. Realmente não quebra o encapsulamento, porque a unidade de implantação no .NET é um assembly.
Mas há o InternalsVisibleTo Attribute (otherAssembly), que age como um mecanismo amigo de assemblagem cruzada . A Microsoft usa isso para montagens de designer visual .
fonte
Amigos também são úteis para retornos de chamada. Você pode implementar retornos de chamada como métodos estáticos
onde
callback
chamadaslocalCallback
internamente, e oclientData
tem sua instância. Na minha opinião,ou...
O que isso permite é que o amigo seja definido puramente no cpp como uma função no estilo c, e não desorganize a classe.
Da mesma forma, um padrão que eu tenho visto com frequência é colocar todos os membros realmente privados de uma classe em outra classe, declarada no cabeçalho, definida no cpp e amiga. Isso permite que o codificador oculte grande parte da complexidade e do trabalho interno da classe do usuário do cabeçalho.
No cabeçalho:
No cpp,
Torna-se mais fácil ocultar coisas que o rio abaixo não precisa ver dessa maneira.
fonte