Quando você deve usar 'friend' em C ++?


354

Eu tenho lido as Perguntas frequentes sobre C ++ e fiquei curioso sobre a frienddeclaraçã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?


5
Embora eu concorde com a resposta de que uma classe de amigo não é necessariamente uma coisa ruim, costumo tratá-la como um código pequeno. Geralmente, embora nem sempre, indica que a hierarquia de classes precisa ser reconsiderada.
MAWG diz Reintegrar Monica

11
Você usaria uma classe de amigo em que já existe um acoplamento rígido. É para isso que é feito. Por exemplo, uma tabela de banco de dados e seus índices estão fortemente acoplados. Quando uma tabela é alterada, todos os seus índices devem ser atualizados. Portanto, a classe DBIndex declararia DBTable como um amigo para que o DBTable possa acessar diretamente os índices internos. Mas não haveria interface pública para DBIndex; não faz sentido sequer ler um índice.
shawnhcorey

Os "puristas" da POO com pouca experiência prática argumentam que um amigo viola os princípios da POO, porque uma classe deve ser a única mantenedora de seu estado privado. Isso é bom, até que você encontre uma situação comum em que duas classes precisem manter um estado privado compartilhado.
kaalus 11/03

Respostas:


335

Em primeiro lugar (IMO) não ouça as pessoas que dizem que friendnã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 friendespecificador 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.

class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;

public:

  string name( void );

protected:

  void setName( string newName );
};

114
Como uma observação extra, a FAQ do C ++ menciona que friend aprimora o encapsulamento. friendconcede acesso seletivo aos membros, assim como protectedfaz. Qualquer controle refinado é melhor do que conceder acesso público. Outros idiomas também definem mecanismos de acesso seletivo, considere C # 's internal. A maioria das críticas negativas sobre o uso de friendestá 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 e friendfornece esse poder.
André Caron

5
Você poderia dizer mais sobre (classes concretas no nível do cpp) e (typedefs mascarados), Andrew ?
OmarOthman

18
Essa resposta parece estar mais focada em explicar o que 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.
bames53

4
Tão eficazmente "amigo" existe porque o C ++ não tem noção de um pacote no qual todos os membros possam compartilhar detalhes da implementação? Eu estaria realmente interessado em um exemplo do mundo real.
weberc2

11
Eu acho que o exemplo Mãe / Filho é infeliz. Esses nomes são adequados para instâncias, mas não para classes. (O problema mostra se temos 2 mães e cada uma tem seu próprio filho).
Jo Então,

162

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.


É exatamente para isso que eu uso. Isso ou apenas define as variáveis ​​de membro como protegidas. É apenas uma vergonha que não funciona para C ++ / CLI :-(
Jon gaiola

12
Pessoalmente, eu desencorajaria isso. Normalmente você está testando uma interface, ou seja, um conjunto de entradas fornece o conjunto esperado de saídas. Por que você precisa inspecionar dados internos?
Graeme

55
@ Graeme: Porque um bom plano de teste inclui testes de caixa branca e caixa preta.
Ben Voigt

11
Costumo concordar com @Graeme, conforme explicado perfeitamente nesta resposta .
Alexis Leclerc

2
@Graeme, pode não ser um dado interno diretamente. Eu posso ser um método que executa uma operação ou tarefa específica nesses dados, em que esse método é privado para a classe e não deve ser acessível ao público, enquanto outro objeto pode precisar alimentar ou propagar o método protegido dessa classe com seus próprios dados.
22617 Francis Cugler

93

A friendpalavra-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:

namespace utils {
    class f {
    private:
        typedef int int_type;
        int_type value;

    public:
        // let's assume it doesn't only need .value, but some
        // internal stuff.
        friend f operator+(f const& a, f const& b) {
            // name resolution finds names in class-scope. 
            // int_type is visible here.
            return f(a.value + b.value);
        }

        int getValue() const { return value; }
    };
}

int main() {
    utils::f a, b;
    std::cout << (a + b).getValue(); // valid
}

Classe base privada de CRTP

Às vezes, você encontra a necessidade de uma política precisar acessar a classe derivada:

// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
    void doSomething() {
        // casting this to Derived* requires us to see that we are a 
        // base-class of Derived.
        some_type const& t = static_cast<Derived*>(this)->getSomething();
    }
};

// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
    // we derive privately, so the base-class wouldn't notice that, 
    // (even though it's the base itself!), so we need a friend declaration
    // to make the base a friend of us.
    friend class SomePolicy<FlexibleClass>;

    void doStuff() {
         // calls doSomething of the policy
         this->doSomething();
    }

    // will return useful information
    some_type getSomething();
};

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.


Olá, recebo um erro de sintaxe (no xcode 4) quando tento seu CRTP. O Xcode acredita que estou tentando herdar um modelo de classe. O erro ocorre P<C>em template<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?
bennedich 22/10/12

@ bennedich, à primeira vista, parece o tipo de erro que você obteria com o suporte insuficiente aos recursos de C ++. O que é bastante comum entre os compiladores. O uso de FlexibleClassinside FlexibleClassdeve implicitamente se referir ao seu próprio tipo.
usar o seguinte comando

@bennedich: As regras para o uso do nome de um modelo de classe de dentro do corpo da classe foram alteradas com o C ++ 11. Tente ativar o modo C ++ 11 no seu compilador.
amigos estão dizendo sobre ben

No Visual Studio 2015, adicione este público: f () {}; f (int_type t): valor (t) {}; Para evitar esse erro do compilador: erro C2440: '<style-style-cast>': não é possível converter de 'utils :: f :: int_type' para 'utils :: f' Nota: Nenhum construtor pode usar o tipo de origem ou o construtor resolução de sobrecarga era ambígua
Damian

41

@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 classe foo".

friendsubstitui o uso de public, não o uso de private!

Na verdade, o FAQ do C ++ já responde a isso .


14
"! substitui amigo usar de público, não usar da privada", eu segundo que
Waleed Eissa

26
@Assaf: sim, mas o FQA é, em grande parte, um monte de bobagens incoerentes e irritadas, sem nenhum valor real. A parte ligada friendnã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.
21911 Konrad Rudolph

12
A maior parte desse FQA é blx absoluta :)
rama-jka Toti

11
@ Konrad: "A única observação real aqui é que o C ++ garante o encapsulamento apenas no tempo de compilação." Algum idioma garante isso em tempo de execução? Tanto quanto eu sei, é permitido retornar referências a membros privados (e funções, para linguagens que permitem que ponteiros funcionem ou funcionem como objetos de primeira classe) em C #, Java, Python e muitos outros.
André Caron

@ André: a JVM e o CLR realmente podem garantir isso até onde eu sei. Não sei se sempre é feito, mas você pode proteger pacotes / assemblies contra essa invasão (nunca fiz isso sozinho).
Konrad Rudolph

27

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.

  • Seus amigos não são amigos do seu filho.
  • Os amigos do seu filho não são seus amigos.
  • Somente amigos podem tocar suas partes íntimas.

" O exemplo canônico é sobrecarregar o operador <<. " O canônico de não usar, friendeu acho.
precisa

16

edit: Lendo o FAQ um pouco mais Gosto da idéia do operador << >> sobrecarregar e adicionar como amigo dessas classes, no entanto, não tenho certeza de como isso não quebra o encapsulamento

Como isso quebraria o encapsulamento?

Você quebra o encapsulamento ao permitir acesso irrestrito a um membro de dados. Considere as seguintes classes:

class c1 {
public:
  int x;
};

class c2 {
public:
  int foo();
private:
  int x;
};

class c3 {
  friend int foo();
private:
  int x;
};

c1é , obviamente, não encapsulado. Qualquer um pode ler e modificar xnele. Não temos como impor nenhum tipo de controle de acesso.

c2é obviamente encapsulado. Não há acesso público a x. Tudo o que você pode fazer é chamar a foofunçã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 c2fez. E assim c2, 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". Assim c2, 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.

friendnã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:

class c4 {
public:
  int getx();
  void setx(int x);
private:
  int x;
};

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.


10

Outra versão comum do exemplo de Andrew, o temido código-dístico

parent.addChild(child);
child.setParent(parent);

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:

class Parent;

class Object {
private:
    void setParent(Parent&);

    friend void addChild(Parent& parent, Object& child);
};

class Parent : public Object {
private:
     void addChild(Object& child);

     friend void addChild(Parent& parent, Object& child);
};

void addChild(Parent& parent, Object& child) {
    if( &parent == &child ){ 
        wetPants(); 
    }
    parent.addChild(child);
    child.setParent(parent);
}

Em outras palavras, você pode manter as interfaces públicas menores e aplicar invariantes que atravessam classes e objetos nas funções de amigo.


6
Por que alguém precisaria de um amigo para isso? Por que não deixar que a addChildfunção de membro também defina o pai?
Nawaz

11
Um exemplo melhor seria fazer setParentum amigo, pois você não deseja permitir que os clientes alterem o pai, pois você o gerenciará na categoria addChild/ removeChildde funções.
Ylisar 26/02

8

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.

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

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.


2
Como é frienduma 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.
jalf

friend está o mais próximo possível do acesso ao nível de pacote C # / Java em C ++. @ jalf - e as classes de amigos (como as de fábrica)?
Ogre Psalm33

11
@ Ogre: E eles? Você ainda está dando especificamente a essa classe e mais ninguém acesso aos internos da classe. Você não está apenas deixando o portão aberto para que códigos desconhecidos arbitrários mexam com sua classe.
jalf

8

Encontrei um lugar útil para usar o acesso de amigos: o mais pequeno de funções privadas.


Mas uma função pública também pode ser usada para isso? Qual é a vantagem de usar o acesso a amigos?
Zheng Qu

@Maverobot Você poderia elaborar sua pergunta?
VladimirS

5

O amigo é útil quando você está construindo um contêiner e deseja implementar um iterador para essa classe.


4

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:

         Game
        /    \
 TwoPlayer  SinglePlayer

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.


4

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.


" operadores << e >> são o exemplo canônico " Não. Antes, exemplos contrários canônicos .
precisa

@curiousguy: operadores <<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.
Gorpik

porque torná-los membros os tornaria difíceis de usar. ” Obviamente, tornar operator<<e operator>>membros da classe de valor, em vez de não membros, ou membros de i|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.
23412

4

O criador do C ++ diz que não está violando nenhum princípio de encapsulamento, e vou citá-lo:

"Amigo" viola o encapsulamento? Não, não tem. "Amigo" é um mecanismo explícito para conceder acesso, assim como a associação. Você não pode (em um programa em conformidade padrão) conceder a si mesmo acesso a uma classe sem modificar sua fonte.

É mais do que claro ...


@curiousguy: Mesmo no caso de modelos, é verdade.
Nawaz

A amizade com o @Nawaz Template pode ser concedida, mas qualquer pessoa pode fazer uma nova especialização parcial ou explícita sem modificar a classe de concessão de amizade. Mas tenha cuidado com as violações de ODR ao fazer isso. E não faça isso de qualquer maneira.
precisa

3

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 :

 class Fred;

 class FredBase {
 private:
   friend class Fred;
   FredBase() { }
 };

 class Fred : private virtual FredBase {
 public:
   ...
 }; 

3

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 .

"Amigo" é um mecanismo explícito para conceder acesso, assim como a associação.


3

Você precisa ter muito cuidado com quando / onde usa a friendpalavra - chave e, como você, eu a uso muito raramente. Abaixo estão algumas notas sobre o uso friende as alternativas.

Digamos que você queira comparar dois objetos para ver se são iguais. Você pode:

  • Use métodos de acessador para fazer a comparação (verifique todos os ivar e determine a igualdade).
  • Ou você pode acessar todos os membros diretamente, tornando-os públicos.

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 friendpalavra-chave:

class Beer {
public:
    friend bool equal(Beer a, Beer b);
private:
    // ...
};

O método equal(Beer, Beer)agora tem acesso direto ao ae b's membros privados (que pode ser char *brand, float percentAlcoholetc. Este é um exemplo bastante artificial, você preferiria se aplicam frienda uma sobrecarga == operator, mas nós vamos chegar a isso.

Algumas coisas a serem observadas:

  • A friendNÃO é uma função membro da classe
  • É uma função comum com acesso especial aos membros privados da classe
  • Não substitua todos os acessadores e mutadores por amigos (você também pode fazer tudo public!)
  • A amizade não é recíproca
  • Amizade não é transitiva
  • A amizade não é herdada
  • Ou, como explica a FAQ do C ++ : "Só porque concedo a você acesso a mim, não concede automaticamente acesso a seus filhos, não concede automaticamente a seus amigos o acesso a mim e não concede automaticamente acesso a você . "

Eu realmente uso apenas friendsquando é muito mais difícil fazê-lo de outra maneira. Como outro exemplo, muitos de matemática vetor funções são muitas vezes criados como friendsdevido à interoperabilidade dos Mat2x2, 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, friendgeralmente é útil quando aplicado ao <<(realmente útil para depuração) >>e talvez ao ==operador, mas também pode ser usado para algo como isto:

class Birds {
public:
    friend Birds operator +(Birds, Birds);
private:
    int numberInFlock;
};


Birds operator +(Birds b1, Birds b2) {
    Birds temp;
    temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
    return temp;
}

Como já disse, não uso com friendmuita frequência, mas de vez em quando é exatamente o que você precisa. Espero que isto ajude!


2

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.


" No que diz respeito ao operador << e operador >>, não há boas razões para fazer com que esses operadores sejam amigos. " Absolutamente correto. " Isso oferece o benefício adicional de permitir que você virtualize essas funções ". Se a classe em questão se destina à derivação, sim. Caso contrário, por que se preocupar?
precisa

Realmente não entendo por que essa resposta foi rebaixada duas vezes - e sem sequer uma explicação! Isso é rude.
curiousguy

Virtual gostaria de acrescentar um hit perf que poderia ser muito grande na serialização
paulm

2

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:

template<typename T>
class FriendIdentity {
public:
  typedef T me;
};

/**
 * A class to get access to protected stuff in unittests. Don't use
 * directly, use friendMe() instead.
 */
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
  Friender() {}
  virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
  friend ToFriend;
#else
  friend class FriendIdentity<ToFriend>::me;
#endif
};

/**
 * Gives access to protected variables/functions in unittests.
 * Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
 */
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> & 
friendMe(Tester * me, ParentClass & instance)
{
    return (Friender<Tester, ParentClass> &)(instance);
}

Isso me permite fazer o seguinte:

friendMe(this, someClassInstance).someProtectedFunction();

Trabalha em GCC e MSVC pelo menos.


2

No C ++, a palavra-chave "friend" é útil na sobrecarga do operador e no Making Bridge.

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:

   1.Overload "operator << ()" funciona na classe "ostream".
   2.Overload "operator << ()" funciona na classe "Point".
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".
É por isso que a segunda é a melhor opção. Agora o compilador pode chamar a "operator <<()"função:

   Como usar o objeto ostream cout.As: cout.operator << (Pointobj) (formulário classe ostream). 
Como chamar operador << (cout, Pointobj) (da classe Point).

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.


2

Como a referência para declaração de amigo diz:

A declaração de amigo aparece em um corpo de classe e concede a uma função ou outra classe acesso a membros privados e protegidos da classe em que a declaração de amigo aparece.

Assim, como lembrete, existem erros técnicos em algumas das respostas que dizem que friendsó podem visitar membros protegidos .


1

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.


1

Para fazer TDD muitas vezes, usei a palavra-chave 'friend' em C ++.
Um amigo pode saber tudo sobre mim?

Não, é apenas uma amizade unidirecional: `(


1

Uma instância específica em que uso friendé ao criar classes Singleton . A friendpalavra-chave permite criar uma função de acessador, que é mais concisa do que sempre ter um método "GetInstance ()" na classe.

/////////////////////////
// Header file
class MySingleton
{
private:
    // Private c-tor for Singleton pattern
    MySingleton() {}

    friend MySingleton& GetMySingleton();
}

// Accessor function - less verbose than having a "GetInstance()"
//   static function on the class
MySingleton& GetMySingleton();


/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
    static MySingleton theInstance;
    return theInstance;
}

Isso pode ser uma questão de gosto, mas não acho que salvar algumas teclas justifique o uso de um amigo aqui. GetMySingleton () deve ser um método estático da classe.
Gorpik

O c-tor privado não permitiria uma função não-amiga para instanciar o MySingleton, portanto a palavra-chave friend é necessária aqui.
JBRWilkinson 07/09/09

@Gorpik " Isso pode ser uma questão de gosto, mas não acho que salvar algumas teclas justifique o uso de um amigo aqui. " Sim . De qualquer forma, friendse não precisar de uma "justificação" particular, ao adicionar uma função membro não.
precisa

Singletons são considerados uma má prática de qualquer maneira (o Google "singleton prejudicial" e você obterá muitos resultados como esse . Não acho que o uso de um recurso para implementar um antipadrão possa ser considerado um bom uso desse recurso.
weberc2

1

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:

Point p;
cout << p;

No entanto, isso pode exigir acesso aos dados privados do Point, portanto, definimos o operador sobrecarregado

friend ostream& operator<<(ostream& output, const Point& p);

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:

class A
{
public:
    void need_your_data(B & myBuddy)
    {
        myBuddy.take_this_name(name_);
    }
private:
    string name_;
};

class B
{
public:
    void print_buddy_name(A & myBuddy)
    {
        myBuddy.need_your_data(*this);
    }
    void take_this_name(const string & name)
    {
        cout << name;
    }
}; 

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.


1

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

class ClubHouse {
public:
    friend class VIPMember; // VIP Members Have Full Access To Class
private:
    unsigned nonMembers_;
    unsigned paidMembers_;
    unsigned vipMembers;

    std::vector<Member> members_;
public:
    ClubHouse() : nonMembers_(0), paidMembers_(0), vipMembers(0) {}

    addMember( const Member& member ) { // ...code }   
    void updateMembership( unsigned memberID, Member::MembershipType type ) { // ...code }
    Amenity getAmenity( unsigned memberID ) { // ...code }

protected:
    void joinVIPEvent( unsigned memberID ) { // ...code }

}; // ClubHouse

A classe dos membros

class Member {
public:
    enum MemberShipType {
        NON_MEMBER_PAID_EVENT,   // Single Event Paid (At Door)
        PAID_MEMBERSHIP,         // Monthly - Yearly Subscription
        VIP_MEMBERSHIP,          // Highest Possible Membership
    }; // MemberShipType

protected:
    MemberShipType type_;
    unsigned id_;
    Amenity amenity_;
public:
    Member( unsigned id, MemberShipType type ) : id_(id), type_(type) {}
    virtual ~Member(){}
    unsigned getId() const { return id_; }
    MemberShipType getType() const { return type_; }
    virtual void getAmenityFromClubHouse() = 0       
};

class NonMember : public Member {
public:
   explicit NonMember( unsigned id ) : Member( id, MemberShipType::NON_MEMBER_PAID_EVENT ) {}   

   void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class PaidMember : public Member {
public:
    explicit PaidMember( unsigned id ) : Member( id, MemberShipType::PAID_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }
};

class VIPMember : public Member {
public:
    friend class ClubHouse;
public:
    explicit VIPMember( unsigned id ) : Member( id, MemberShipType::VIP_MEMBERSHIP ) {}

    void getAmenityFromClubHouse() override {
       Amenity = ClubHouse::getAmenity( this->id_ );
    }

    void attendVIPEvent() {
        ClubHouse::joinVIPEvent( this->id );
    }
};

Facilidades

class Amenity{};

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.


0

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.


0

Você pode usar amizade quando classes diferentes (que não herdam uma da outra) estão usando membros particulares ou protegidos da outra classe.

Casos de uso típicos de funções de amigo são operações conduzidas entre duas classes diferentes, acessando membros privados ou protegidos de ambas.

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.

// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}

0

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.


-1

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 .


-1

Amigos também são úteis para retornos de chamada. Você pode implementar retornos de chamada como métodos estáticos

class MyFoo
{
private:
    static void callback(void * data, void * clientData);
    void localCallback();
    ...
};

onde callbackchamadas localCallbackinternamente, e oclientData tem sua instância. Na minha opinião,

ou...

class MyFoo
{
    friend void callback(void * data, void * callData);
    void localCallback();
}

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:

class MyFooPrivate;
class MyFoo
{
    friend class MyFooPrivate;
public:
    MyFoo();
    // Public stuff
private:
    MyFooPrivate _private;
    // Other private members as needed
};

No cpp,

class MyFooPrivate
{
public:
   MyFoo *owner;
   // Your complexity here
};

MyFoo::MyFoo()
{
    this->_private->owner = this;
}

Torna-se mais fácil ocultar coisas que o rio abaixo não precisa ver dessa maneira.


11
As interfaces não seriam uma maneira mais limpa de conseguir isso? O que é para impedir alguém de procurar MyFooPrivate.h?
JBRWilkinson 07/09/09

11
Bem, se você estiver usando privado e público para guardar segredos, será derrotado facilmente. Ao "esconder", quero dizer, o usuário do MyFoo realmente não precisa ver os membros privados. Além disso, é útil manter a compatibilidade ABI. Se você tornar _private um ponteiro, a implementação privada poderá mudar o quanto você quiser, sem tocar na interface pública, mantendo assim a compatibilidade ABI.
shash 16/09/09

Você está se referindo ao idioma PIMPL; o objetivo para o qual não está o encapsulamento adicional, como você parece estar dizendo, mas mover os detalhes da implementação para fora do cabeçalho, para que a alteração de um detalhe da implementação não force a recompilação do código do cliente. Além disso, não há necessidade de usar o amigo para implementar esse idioma.
weberc2

Bem, sim. Seu principal objetivo é mover os detalhes da implementação. O amigo de lá é útil para lidar com membros privados dentro da classe pública, do privado ou do contrário.
Shash 11/03/15
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.