C++ friend
Na aula public e private, você aprendeu que os membros private de uma classe só podem ser usados pela própria classe. É uma regra ótima: protege os dados de mudanças erradas. Mas, de vez em quando, uma função de fora precisa mesmo enxergar o que está lá dentro, como o operator<< que imprime o objeto. Para esses casos, a classe pode abrir uma exceção e declarar alguém como amigo: friend. Nesta aula você vai ver como, quando, e por que usar pouco.
- O que é uma função amiga e como declarar uma:
friend void auditar(const Conta& c);. - Que a função amiga não é um método: não tem
thise não se chama com ponto. - O uso mais comum:
operator<<e outros operadores declarados comofrienddentro da classe. - O que é uma classe amiga:
friend class Gerente;. - As regras: quem concede é a classe; a amizade não é recíproca, não é herdada e não passa adiante.
- Por que
frienddeve ser usado com moderação.
Os programas foram compilados com o g++ 15 na máquina da Ana, na pasta ~/cpp, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: a chave reserva
A casa da Ana é trancada: só ela entra (é o private). Mas ela deixa uma chave reserva com a vizinha de confiança, para regar as plantas quando viaja. Repare em três detalhes:
- Quem decide é a dona da casa. A vizinha não pode "se declarar" com direito à chave. Em C++, é a classe que escreve
friend, nunca o amigo. - A vizinha não mora lá. Ela entra, mas não vira parte da família. A função amiga acessa os privados, mas não é um método da classe.
- A chave é pessoal. O filho da vizinha não ganha a chave, e a Ana não ganha a chave da casa da vizinha. A amizade não é herdada nem recíproca.
1. Função amiga
Uma Conta bancária guarda o titular e o saldo como privados. Uma função de auditoria, que não faz parte da conta, precisa ler os dois. Primeiro, sem friend:
#include <iostream>
#include <string>
class Conta {
public:
Conta(std::string titular, double saldo) : titular_{titular}, saldo_{saldo} {}
void depositar(double valor) { if (valor > 0) saldo_ += valor; }
private:
std::string titular_;
double saldo_;
};
void auditar(const Conta& c) {
std::cout << "Auditoria: " << c.titular_ << " tem R$ " << c.saldo_ << '\n';
}
int main() {
Conta ana{"Ana", 150};
ana.depositar(50);
auditar(ana);
}
$ g++ -std=c++23 -Wall -Wextra semamiga.cpp -o semamiga
semamiga.cpp: In function ‘void auditar(const Conta&)’:
semamiga.cpp:15:37: error: ‘std::string Conta::titular_’ is private within this context
15 | std::cout << "Auditoria: " << c.titular_ << " tem R$ " << c.saldo_ << '\n';
| ^~~~~~~~
semamiga.cpp:10:17: note: declared private here
10 | std::string titular_;
| ^~~~~~~~
semamiga.cpp:15:65: error: ‘double Conta::saldo_’ is private within this context
15 | std::cout << "Auditoria: " << c.titular_ << " tem R$ " << c.saldo_ << '\n';
| ^~~~~~
semamiga.cpp:11:12: note: declared private here
11 | double saldo_;
| ^~~~~~
Dois erros iguais, um para cada atributo: "Conta::titular_ é privado neste contexto". O contexto é a função auditar, que está fora da classe. (Clang: 'titular_' is a private member of 'Conta'.) Agora, com uma linha a mais dentro da classe:
#include <iostream>
#include <string>
class Conta {
public:
Conta(std::string titular, double saldo) : titular_{titular}, saldo_{saldo} {}
void depositar(double valor) { if (valor > 0) saldo_ += valor; }
friend void auditar(const Conta& c); // auditar é amiga da Conta
private:
std::string titular_;
double saldo_;
};
void auditar(const Conta& c) {
std::cout << "Auditoria: " << c.titular_ << " tem R$ " << c.saldo_ << '\n';
}
int main() {
Conta ana{"Ana", 150};
ana.depositar(50);
auditar(ana);
}
- friend
- "Amigo". Escrito dentro da classe que concede o acesso.
- void auditar(const Conta& c)
- A assinatura completa da função que vira amiga. Ela tem que bater com a função escrita lá embaixo.
- em qual seção?
- Tanto faz:
public,privateouprotected. A declaraçãofriendnão é um membro, então o nível de acesso não se aplica a ela. Muita gente a coloca no fim da partepublic, para ficar visível. - void auditar(const Conta& c) { … }
- A função em si, fora da classe e sem a palavra
friend. Agora ela pode lerc.titular_ec.saldo_. - auditar(ana);
- Chamada como uma função comum, passando o objeto. Não é
ana.auditar().
$ g++ -std=c++23 -Wall -Wextra amiga.cpp -o amiga $ ./amiga Auditoria: Ana tem R$ 200
Um método roda "dentro" de um objeto: tem this e acessa os atributos pelo nome (saldo_). Uma função amiga é uma função comum que só ganhou permissão: ela não tem this e precisa receber o objeto como parâmetro (c.saldo_). Por isso, ana.auditar() dá erro: ‘class Conta’ has no member named ‘auditar’ ("a classe Conta não tem membro chamado auditar").
Experimente: rode como está. Depois apague a linha do friend e leia os erros. Volte com ela e troque auditar(ana); por ana.auditar();: qual é a mensagem agora?
#include <iostream>
#include <string>
class Conta {
public:
Conta(std::string titular, double saldo) : titular_{titular}, saldo_{saldo} {}
void depositar(double valor) { if (valor > 0) saldo_ += valor; }
friend void auditar(const Conta& c);
private:
std::string titular_;
double saldo_;
};
void auditar(const Conta& c) {
std::cout << "Auditoria: " << c.titular_ << " tem R$ " << c.saldo_ << '\n';
}
int main() {
Conta ana{"Ana", 150};
ana.depositar(50);
auditar(ana);
}
2. O uso típico: operadores amigos
Na aula Sobrecarga de Operadores, o operator<< do Dinheiro foi escrito fora da classe e usou o método público centavos(). Mas e se a classe não tiver (nem deva ter) métodos públicos para cada atributo? É o caso de uma Fracao: ninguém precisa de numerador() espalhado pelo programa, mas imprimir e multiplicar frações precisa ver o numerador e o denominador. O jeito mais comum é declarar e escrever o operador amigo dentro da própria classe:
#include <iostream>
class Fracao {
public:
Fracao(int numerador, int denominador) : num_{numerador}, den_{denominador} {}
friend std::ostream& operator<<(std::ostream& saida, const Fracao& f) {
return saida << f.num_ << '/' << f.den_;
}
friend Fracao operator*(const Fracao& a, const Fracao& b) {
return Fracao{a.num_ * b.num_, a.den_ * b.den_};
}
private:
int num_;
int den_;
};
int main() {
Fracao metade{1, 2};
Fracao tres_quartos{3, 4};
std::cout << metade << " de " << tres_quartos << " = " << metade * tres_quartos << '\n';
}
$ g++ -std=c++23 -Wall -Wextra fracao.cpp -o fracao $ ./fracao 1/2 de 3/4 = 3/8
Mesmo escritas dentro das chaves da classe, essas duas funções não são métodos: o friend na frente faz delas funções livres, com dois parâmetros cada. É por isso que o operator<< aqui é válido, enquanto o mesmo código sem friend dava must have exactly one argument (veja a aula de operadores). Essa forma tem até apelido: amigo escondido (hidden friend), porque só é encontrada quando um dos lados é uma Fracao, o que deixa as mensagens de erro de outras classes mais curtas.
| Forma | Quando usar |
|---|---|
| Função livre que usa métodos públicos | A classe já tem métodos públicos suficientes (como Dinheiro::centavos()). É a forma que mais preserva o encapsulamento. |
friend declarado e escrito dentro da classe | O operador precisa dos privados e faz parte "natural" da classe (<<, ==, +). É o uso mais comum de friend. |
friend declarado dentro, escrito fora | Igual ao anterior, quando o corpo é grande ou fica num .cpp separado (aula Arquivos .h e .cpp). |
3. Classe amiga
Às vezes, não é uma função, mas uma classe inteira que precisa trabalhar junto. Com friend class, todos os métodos da outra classe ganham acesso:
#include <iostream>
#include <string>
class Cofre {
public:
Cofre(std::string segredo) : segredo_{segredo} {}
private:
std::string segredo_;
bool aberto_ = false;
friend class Gerente; // Gerente pode tudo no Cofre
};
class Gerente {
public:
void abrir(Cofre& c) {
c.aberto_ = true;
std::cout << "Cofre aberto. Segredo: " << c.segredo_ << '\n';
}
void fechar(Cofre& c) {
c.aberto_ = false;
std::cout << "Cofre fechado.\n";
}
};
int main() {
Cofre cofre{"receita do bolo da vovó"};
Gerente bia;
bia.abrir(cofre);
bia.fechar(cofre);
}
$ g++ -std=c++23 -Wall -Wextra cofre.cpp -o cofre $ ./cofre Cofre aberto. Segredo: receita do bolo da vovó Cofre fechado.
Os usos reais de classe amiga costumam ser pares muito unidos: uma lista e o seu iterador (aula Iteradores), uma classe e a classe que monta objetos dela, uma classe e os seus testes automáticos. Também dá para ser mais econômico e liberar só um método de outra classe: friend void Gerente::abrir(Cofre&);. Para isso, a classe Gerente precisa estar completa (definida, com as chaves) antes da Cofre.
4. As regras da amizade
| Regra | No exemplo |
|---|---|
| Quem concede é a classe. Não existe jeito de uma função ou classe se declarar amiga de outra. | Só o Cofre pode escrever friend class Gerente;. |
| Não é recíproca. | O Gerente vê os privados do Cofre; o Cofre não vê os do Gerente. |
| Não é herdada. | Uma classe filha de Gerente não é amiga do Cofre. |
| Não passa adiante. | Se o Gerente tem um amigo, esse amigo não ganha acesso ao Cofre. |
Veja a regra da herança na prática. Um Estagiario é filho de Gerente:
class Estagiario : public Gerente {
public:
void espiar(const Cofre& c) {
std::cout << "Espiando: " << c.segredo_ << '\n';
}
};
$ g++ -std=c++23 -Wall -Wextra herdada.cpp -o herdada
herdada.cpp: In member function ‘void Estagiario::espiar(const Cofre&)’:
herdada.cpp:23:40: error: ‘std::string Cofre::segredo_’ is private within this context
23 | std::cout << "Espiando: " << c.segredo_ << '\n';
| ^~~~~~~~
herdada.cpp:8:17: note: declared private here
8 | std::string segredo_;
| ^~~~~~~~
O Estagiario herdou os métodos do Gerente (e pode chamar abrir, que roda como código do Gerente), mas o código escrito nele não tem a chave.
5. Por que usar com moderação
Cada friend é um furo controlado no encapsulamento. A classe deixa de ser a única responsável pelos próprios dados: se a Conta tem a regra "saldo nunca negativo" e uma função amiga mexe em saldo_ direto, a regra só vale se a amiga também a respeitar. E, quando você mudar o nome ou o tipo de um atributo, terá de corrigir todos os amigos.
friend, pergunte
- Um método público resolve? Se a função só precisa ler um valor, um método
const(comosaldo()) é mais simples e seguro. - Isso devia ser um método? Se a função só trabalha com um objeto e não é um operador, provavelmente sim:
conta.extrato()em vez deextrato(conta)amiga. - É um operador ou um par de classes inseparáveis? Esses são os bons motivos para
friend.
Uma classe com muitos amigos é um sinal de que os dados estão no lugar errado.
Quando dá errado
O que significa: a função não é membro nem amiga da classe. Confira também se a assinatura da declaração friend é idêntica à da função: friend void auditar(Conta& c); (sem const) declara outra função, e a sua continua sem acesso.
Como consertar: prefira usar métodos públicos; se for mesmo o caso, acrescente a declaração friend com a assinatura exata.
$ g++ -std=c++23 -Wall -Wextra metodo.cpp -o metodo
metodo.cpp: In function ‘int main()’:
metodo.cpp:22:9: error: ‘class Conta’ has no member named ‘auditar’
22 | ana.auditar();
| ^~~~~~~
O que significa: você chamou a função amiga como se fosse um método. Amiga não é membro. (Clang: no member named 'auditar' in 'Conta'.)
Como consertar: auditar(ana);.
friend std::ostream& operator<<(std::ostream& saida, const Fracao& f) {
return saida << num_ << '/' << den_;
}
$ g++ -std=c++23 -Wall -Wextra semobjeto.cpp -o semobjeto
semobjeto.cpp: In function ‘std::ostream& operator<<(std::ostream&, const Fracao&)’:
semobjeto.cpp:8:25: error: invalid use of non-static data member ‘Fracao::num_’
8 | return saida << num_ << '/' << den_;
| ^~~~
semobjeto.cpp:11:9: note: declared here
11 | int num_;
| ^~~~
semobjeto.cpp:8:40: error: invalid use of non-static data member ‘Fracao::den_’
8 | return saida << num_ << '/' << den_;
| ^~~~
semobjeto.cpp:12:9: note: declared here
12 | int den_;
| ^~~~
semobjeto.cpp:7:72: warning: unused parameter ‘f’ [-Wunused-parameter]
7 | friend std::ostream& operator<<(std::ostream& saida, const Fracao& f) {
| ~~~~~~~~~~~~~~^
O que significa: "uso inválido do atributo num_". A função está escrita dentro da classe, mas, por ser amiga, não tem this: ela não sabe de qual fração é o num_. O aviso unused parameter ‘f’ é a pista: a fração chegou no parâmetro f e ninguém a usou.
Como consertar: f.num_ e f.den_.
O que significa: amizade não é herdada (seção 4).
Como consertar: faça a classe base amiga oferecer um método protected que faz o trabalho, ou declare a filha também como amiga, se fizer sentido.
Missão
Missão: a Fracao já sabe multiplicar, mas não sabe se imprimir. Escreva, dentro da classe, um operator<< amigo que mostra numerador/denominador. Depois tire as barras // da linha do main. A saída deve ser 1/2 x 3/4 = 3/8.
#include <iostream>
class Fracao {
public:
Fracao(int numerador, int denominador) : num_{numerador}, den_{denominador} {}
friend Fracao operator*(const Fracao& a, const Fracao& b) {
return Fracao{a.num_ * b.num_, a.den_ * b.den_};
}
// escreva o operator<< amigo aqui
private:
int num_;
int den_;
};
int main() {
Fracao metade{1, 2};
Fracao tres_quartos{3, 4};
// std::cout << metade << " x " << tres_quartos << " = " << metade * tres_quartos << '\n';
}
#include <iostream>
class Fracao {
public:
Fracao(int numerador, int denominador) : num_{numerador}, den_{denominador} {}
friend Fracao operator*(const Fracao& a, const Fracao& b) {
return Fracao{a.num_ * b.num_, a.den_ * b.den_};
}
friend std::ostream& operator<<(std::ostream& saida, const Fracao& f) {
return saida << f.num_ << '/' << f.den_;
}
private:
int num_;
int den_;
};
int main() {
Fracao metade{1, 2};
Fracao tres_quartos{3, 4};
std::cout << metade << " x " << tres_quartos << " = " << metade * tres_quartos << '\n';
}
A saída da solução no laboratório:
$ ./fracao2 1/2 x 3/4 = 3/8
Referência
Código (dentro da classe X) | O que libera |
|---|---|
friend void f(const X& x); | A função livre f acessa os privados de X |
friend std::ostream& operator<<(std::ostream& s, const X& x) { … } | Operador amigo declarado e escrito na classe (amigo escondido) |
friend class Y; | Todos os métodos de Y acessam os privados de X |
friend void Y::m(X&); | Só o método m de Y (com Y declarada antes) |
| Não é membro | Sem this; chamada como f(x), nunca x.f() |
| Não é recíproca, herdada nem transitiva | Cada acesso é concedido explicitamente pela classe |
Exercícios
1. A palavra
Complete a declaração dentro da classe Aluno para a função boletim poder ler as notas privadas:
2. Classe amiga
Complete para a classe Professor acessar tudo da classe Turma (a linha fica dentro da Turma):
3. Chamar a amiga
A função auditar é amiga da Conta. Complete a chamada para auditar a conta bia:
4. A classe Cofre declarou friend class Gerente;. O que é verdade?
5. Qual é o uso mais comum e aceito de friend?
6. Dentro de um friend std::ostream& operator<<(std::ostream& s, const Fracao& f) escrito na classe, como se lê o numerador?
Resumo
friend, escrito dentro de uma classe, dá a uma função ou classe de fora acesso aos membrosprivateeprotected.- Quem concede é sempre a classe. A seção (
public/private) onde ofriendaparece não importa. - Função amiga não é método: não tem
this, recebe o objeto por parâmetro e é chamada comof(obj). - Uso típico: operadores (
<<,*,==) declarados e escritos dentro da classe comfriend. friend class Y;libera todos os métodos deY.- A amizade não é recíproca, não é herdada e não passa adiante.
- Use com moderação: cada amigo é um furo no encapsulamento. Prefira métodos públicos quando eles resolvem.