AULA 13 - Programação II - Graduação

De IFSC
Revisão de 11h22min de 2 de maio de 2018 por imported>Fargoud (→‎Exemplos:)
Ir para navegação Ir para pesquisar

Mecanismo Friend

Uma das principais razões para se usar técnicas de programação orientada a objetos é o isolamento, ou encapsulamento de dados dentro de classes.

PRGencaps2.png

Fazendo assim, apenas funções membro podem obter acesso a valores críticos de atributos da classe.

Na maioria das vezes, esconder dados dentro de classes dá aos programas uma boa medida de controle, impedindo a modificação indiscriminada de valores críticos pelos processos em andamento.

Em C++, existe uma maneira de romper a proteção aos membros de uma classe usando as friends, ou amigas.

PRGencaps3.png

Friend é um atributo especial de acesso a classes. Com ele declaramos que uma função externa, ou outra classe, é amiga da mesma.

Em resumo, as classes do C++ podem declarar dois tipos de friends:

  • Uma classe inteira pode ser friend de uma outra classe
friend class F;  
  • Uma única função pode ser declarada como sendo uma friend
friend F;  


Funções Amigas

Declarar uma função externa "friend" dentro de uma classe, permite que esta possa ler e manipular membros "private" e "protected" (e claro "public" - públicas, o que já seria normalmente permitido, de qualquer maneira) da classe.

Uma função friend pode ser qualquer uma do programa, incluindo funções membros de outra classe.

A função friend pode ser também uma função global do C++.

Se essa função for membro de uma outra classe, apenas essa função, e nenhum outro membro da classe, receberá permissão de acesso aos membros privados da classe que faz a declaração.

Assim, o acesso a membros pode ser restrito para determinadas partes do código.

Declarando uma função específica como amiga de duas classes, dá-se a essa função acesso aos campos privados e protegidos de instâncias de ambas as classes.

Em um projeto típico, a função friend declara parâmetros das duas classes para as quais ela deve a sua amizade.

Dentro da função friend, as instruções podem então acessar normalmente membros escondidos nos parâmetros passados como argumentos para a função.


Sintaxe:

Para fazer com que funções fora do escopo da classe tenham acesso a membros, temos de colocar o protótipo da função externa dentro da classe, precedido da palavra "friend".

Desta forma o compilador passa a ignorar os atributos de restrição de acesso da classe, quando a função acessa membros da mesma.

class NomeClasse{
... 	
friend tipo NomeFuncao(NomeClasse);
...  
};

Exemplos:

// friend functions
#include <iostream>
using namespace std;
class CRectangle
{
   int width, height;
 public:
   void set_values (int, int);
   int area () {return (width * height);}
   friend CRectangle duplicate (CRectangle);
};
void CRectangle::set_values (int a, int b)
{
 width = a;
 height = b;
}
CRectangle duplicate (CRectangle rectparam)
{
 CRectangle rectres;
 rectres.width = rectparam.width*2;
 rectres.height = rectparam.height*2;
 return (rectres);
}
int main ()
{
 CRectangle rect, rectb;
 rect.set_values (2,3);
 cout<< "Area do objeto retangulo original:\n";
 cout << rect.area();
 cout<< "\n";
 rectb = duplicate (rect);
 cout<< "Area do retangulo inicializado a partir da funcao amiga:\n";
 cout << rectb.area();
 cout<< "\n";
 return 0;
}

No código acima, a função externa duplicate() é declarada como Amiga da classe CRectangle.

O código-cliente em main() declara dois objetos, rect e rectb como sendo da classe CRectangle.

O primeiro objeto, rect inicializa normalmente seus atributos privados width e height através do método set_values().

Já o objeto recbt o faz, indiretamente, por meio da função amiga duplicate().



#include <iostream>
using namespace std;
class dois;
class um {
   friend void exibe (um &c1, dois &c2);
 private:
   char *s1;
 public:
   um() { s1 = "Testando..."; }
};
class dois {
   friend void exibe (um &c1, dois &c2);
 private:
   char *s2;
 public:
   dois() { s2 = "Um, Dois, Tres!"; }
};
void main () {
  um c1;
  dois c2;
  exibe (c1, c2);
}
void exibe (um &c1, dois &c2) {
   cout << c1.s1 << c2.s2 << '\n';
}

O exemplo anterior declara duas classes, "um" e "dois". Como cada uma dessas classes irá se referir à outra e porque o C++ requer que sejam declarados os identificadores antes de usá-los, a classe "dois" é declarada antes da "um".

Declarar uma classe antecipadamente diz ao compilador para aceitar referências ao nome da classe antes dela ser declarada formalmente.

As declarações da função "exibe" listam os parâmetros de referência "c1" e "c2" dos dois tipos de classe. Como "exibe" é amigas dessas duas classes, suas instruções podem acessar membros privados e protegidos de argumentos passados para "exibe".


#include <iostream>
using namespace std;
class um;
class dois {
  private:
    char *s2;
  public:
    dois() { s2 = "Um, Dois, Tres!"; }
    void exibe (um &c1);
};
class um {
    friend void dois :: exibe (um &c1);
  private:
    char *s1;
  public:
    um() { s1 = "Testando..."; }
};
void dois :: exibe (um &c1) {
    cout << c1.s1 << s2 << '\n';
}
void main () {
  um c1;
  dois c2;
  c2.exibe (c1);
}


A primeira regra a ser lembrada sobre funções membro friend está relacionada com a ordem das declarações da classe. A classe que prototipa a função membro deve vir antes da classe que declara essa função como sendo um friend. Usar uma declaração antecipada não é suficiente.

Uma outra diferença em relação ao primeiro exemplo desta seção é o modo como a função "exibe" faz referências aos dados privados das duas classes.

A função agora só possui um parâmetro, "one &c1".

Como a função é um membro da classe "dois" não se faz necessário listar um argumento do tipo "dois".

De fato, fazê-lo seria um erro. A função está encapsulada dentro de "dois"; portanto, a instrução da função "exibe" pode se referir ao campo privado "s2" de "dois" diretamente. A referência a "c1.s1" é permitida, pois "exibe" é friend da classe "um".

Como a função "exibe" é um membro de "dois", ela agora possui um ponteiro this que endereça a instância para a qual a função foi chamada.

Consequentemente, o programa não pode mais chamar a função friend diretamente.

Ele agora precisa definir uma variável do tipo "dois" e chamar "exibe" para essa variável.

O uso de funções amigas deve ser evitado sempre que possível, pois diminui a identidade da orientação a objetos. Isto ocorre porque o uso desse mecanismo representa uma quebra no encapsulamento.

Quando passamos a permitir que funções tenham acesso a membros restritos dos objetos fazemos com que agentes externos interfiram na autonomia dos mesmos. Isto pode dificultar a análise de programas muito extensos.

No caso da criação de procedimentos que tem a finalidade de modificar o conteúdo do objeto explicitamente, como nos casos de operadores e modificadores de comportamento, podemos usar as funções amigas "friends" para esta finalidade tomando os devidos cuidados para torná-las muito bem restritas as funções que devem executar.

É muito importante observar se estas funções alteram dados dentro dos objetos que não podem ser modificados. Se as devidas precauções forem tomadas não haverá problemas no uso de funções "friend".


  1. include <iostream>

using namespace std;

class X {

   int i;
 public:
     X() {i=20;      }
     friend class Z;

};

class Z {

    int j;

public:

  Z(){X x; j = x.i+4;}
  getZ() {return j;}

};


int main() { Z zexemplo;

  cout << "Valor de saida: " ;
   cout << zexemplo.getZ();
   cout<< "\n";
   return 0;

}

Classes Amigas

Uma classe friend pode ser qualquer outra classe no sistema, geralmente fora da hierarquia da classe a que pertence a "amiga".

Exemplos:


O tipo de acesso a uma função friend é qualquer um, ou seja, a declaração desta função pode estar entre os membros privados, públicos ou protegidos de uma classe.

Isto não deve causar surpresa, já que uma função friend não pertence à classe e, portanto, não tem qualquer declaração de acesso.

Isto justifica a posição da declaração da função friend nas classes do exemplo anterior.



É importante notar que friends são interessantes para dar a objetos acesso aos campos privados de outros objetos. Usadas cuidadosamente, friends podem melhorar o desempenho, eliminando o desperdício associado com a chamada a funções membro. Todavia, friends também rompem as barreiras que protegem os dados dentro das classes. Usar friends reduz as vantagens da programação orientada a objetos advindas do encapsulamento de funções e dados, e do isolamento de instruções que acessam valores críticos. Por isso, deve-se ter muito cuidado para usar funções friend [4].



  1. include <iostream>

using namespace std;

class X {

   int i;
 public:
     X() {i=20;      }
     friend class Z;

};

class Z {

    int j;

public:

  Z(){X x; j = x.i+4;}
  getZ() {return j;}

};


int main() { Z zexemplo;

  cout << "Valor de saida: " ;
   cout << zexemplo.getZ();
   cout<< "\n";
   return 0;

}


Friend classes Da mesma forma que podemos declarar funções como amigas de uma determinada classe, podemos declarar outra classe como sua amiga. Este artifício faz com que os membros da classe onde foi feita a declaração sejam acessíveis à declarada. Assim, a segunda classe passa a ter possibilidade de manipulação livre dos membros da outra.

Apesar da funcionalidade ser um pouco semelhante à que temos no uso das funções, quando declaramos uma classe como "friend" dentro de outra, teremos todas as funções da primeira com poderes de acesso aos membros da segunda. Esta característica requer cuidado redobrado quando operações da classe "friend" interferem no conteúdo da outra.

// friend class
#include <iostream>
using namespace std;
class CSquare;
class CRectangle 
{
   int width, height;
 public:
   int area ()
     {return (width * height);}
   void convert (CSquare a);      //consigo acessar porque é friend
};
class CSquare 
{
 private:
   int side;
 public:
   void set_side (int a) {side=a;}
   friend class CRectangle;      //declaro friend class
};
void CRectangle::convert (CSquare a) 
{
 width = a.side;
 height = a.side;
}
int main () 
{
 CSquare sqr;
 CRectangle rect;
 sqr.set_side(4);
 rect.convert(sqr);
 cout << rect.area();
 system (“pause”);
 return 0;
}

Nota:

Não há a propriedade de reciprocidade (ou reversão). O facto de uma função ou classe ser friend de uma classe não implica o contrário, ou seja, A ter B como friend não implica que B a tenha em mesma conta. Outra propriedade é que não há transitividade. Se numa class A for declarado que a class B é friend. E depois que na classe B estiver declarado que a classe C é friend de B,…Isto não implica que A seja friend de C.


Sintaxe

       friend class-name;  

friend function-declarator;

Declarações de amigo Se você declarar uma função de amigo que não foi declarada anteriormente, essa função é exportada para o escopo de envolvimento sem classe.

As funções declaradas em uma declaração de amigo são tratadas como se tivessem sido declaradas usando a palavra-chave extern. (Para obter mais informações sobre extern, consulteEspecificadores de classe de armazenamento estático.)

Ainda que as funções com escopo global possam ser declaradas como amigos antes dos protótipos, as funções de membro não podem ser declaradas como amigos antes da aparência de sua declaração completa da classe. O exemplo de código a seguir mostra porque isso falha:

class ForwardDeclared; // Class name is known. class HasFriends {

   friend int ForwardDeclared::IsAFriend();   // C2039 error expected  

};

O exemplo anterior insere o nome da classe ForwardDeclared no escopo, mas a declaração completa — especificamente, a parte que declara a função IsAFriend — não é conhecida. Consequentemente, a declaração friend na classe HasFriends gera um erro.

Para declarar duas classes que são amigas da outro, a segunda classe inteira deve ser especificada como amiga de primeira classe. A razão para essa restrição é que o compilador tem informações suficientes para declarar funções individuais de amigo somente no ponto onde a segunda classe é declarada.

System_CAPS_ICON_note.jpg Observação Embora a segunda classe inteira deve ser amiga da primeira classe, você pode selecionar quais funções na primeira classe serão amigas da segunda classe.

funções friend Uma função friend é uma função que não é membro de uma classe, mas que tem acesso a membros particulares e protegidos da classe. As funções friend não são consideradas membros de classe; elas são funções externas normais que recebem privilégios de acesso especiais. Amigos não estão no escopo da classe, e não são chamadas usando os operadores de seleção de membro (. e –>) a menos que sejam membros de outra classe. Uma função friend é declarada pela classe que está concedendo acesso. A declaração friend pode ser colocada em qualquer lugar da declaração da classe. Ela não é afetada pelas palavras-chave de controle de acesso.

O exemplo a seguir mostra uma classe Point e uma função friend, ChangePrivate. A função friend tem acesso ao membro de dados particular do objeto Point que ela recebe como um parâmetro.

// friend_functions.cpp // compile with: /EHsc

  1. include <iostream>

using namespace std; class Point {

   friend void ChangePrivate( Point & );  

public:

   Point( void ) : m_i(0) {}  
   void PrintPrivate( void ){cout << m_i << endl; }  
 

private:

   int m_i;  

};

void ChangePrivate ( Point &i ) { i.m_i++; }

int main() {

  Point sPoint;  
  sPoint.PrintPrivate();  
  ChangePrivate(sPoint);  
  sPoint.PrintPrivate();  

// Output: 0

          1  

}

Membros de classe como amigos As funções de membro da classe podem ser declaradas como amigos em outras classes. Considere o exemplo a seguir:

// classes_as_friends1.cpp // compile with: /c class B;

class A { public:

  int Func1( B& b );  
 

private:

  int Func2( B& b );  

};

class B { private:

  int _b;  
 
  // A::Func1 is a friend function to class B  
  // so A::Func1 has access to all members of B  
  friend int A::Func1( B& );  

};

int A::Func1( B& b ) { return b._b; } // OK int A::Func2( B& b ) { return b._b; } // C2248

No exemplo anterior, somente a função A::Func1( B& ) tem acesso de amigo para classificar B. Em virtude disso, o acesso ao membro particular _b está correto em Func1 da classe A mas não em Func2.

Uma classe friend é uma classe cujas funções membro são as funções amigas de uma classe, ou seja, cujas funções membro tenham acesso aos outros membros particulares e protegidos da classe. Suponha que a declaração friend na classe B fosse:

friend class A;

Nesse caso, todas as funções membro na classe A receberão acesso de amigo para classificar B. O código a seguir é um exemplo de uma classe de amigo:

// classes_as_friends2.cpp // compile with: /EHsc

  1. include <iostream>

using namespace std; class YourClass { friend class YourOtherClass; // Declare a friend class public:

  YourClass() : topSecret(0){}  
  void printMember() { cout << topSecret << endl; }  

private:

  int topSecret;  

};

class YourOtherClass { public:

  void change( YourClass& yc, int x ){yc.topSecret = x;}  

};

int main() {

  YourClass yc1;  
  YourOtherClass yoc1;  
  yc1.printMember();  
  yoc1.change( yc1, 5 );  
  yc1.printMember();  

}

A amizade não é mútua a menos que explicitamente especificada como tal. No exemplo anterior, as funções membro de YourClass não podem acessar os membros privados de YourOtherClass.

Um tipo gerenciado não pode ter nenhuma função de amigo, classes de amigo ou interfaces de amigo.

A amizade não é herdada, o que significa que as classes derivadas de YourOtherClass não podem acessar os membros privados de YourClass. A amizade não é transitiva, assim as classes que são amigos de YourOtherClass não podem acessar membros privados de YourClass.

A figura a seguir mostra quatro declarações de classe: Base, Derived, aFriend e anotherFriend. Somente a classe aFriend tem acesso direto aos membros privados de Base (e a todos os membros que Base pode ter herdado).

Gráfico de implicações de relacionamento de amigo Implicações da relação de amigo

Definições de amigo embutidas As funções friend podem ser definidas nas declarações de classe. Essas funções são funções embutidas, e como funções membro embutidas, se comportam como se fossem definidas imediatamente após todos os membros de classe terem sido vistos, mas antes que o escopo de classe seja fechado (o final da declaração de classe).

As funções friend definidas dentro de declarações de classe não são consideradas no escopo da classe delimitadora; elas estão no escopo do arquivo.