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.

Você vai aprender
  • 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 this e não se chama com ponto.
  • O uso mais comum: operator<< e outros operadores declarados como friend dentro 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 friend deve 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, private ou protected. A declaração friend não é um membro, então o nível de acesso não se aplica a ela. Muita gente a coloca no fim da parte public, 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 ler c.titular_ e c.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
Amiga não é método

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.

FormaQuando usar
Função livre que usa métodos públicosA classe já tem métodos públicos suficientes (como Dinheiro::centavos()). É a forma que mais preserva o encapsulamento.
friend declarado e escrito dentro da classeO operador precisa dos privados e faz parte "natural" da classe (<<, ==, +). É o uso mais comum de friend.
friend declarado dentro, escrito foraIgual 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

RegraNo 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.

Antes de escrever friend, pergunte
  • Um método público resolve? Se a função só precisa ler um valor, um método const (como saldo()) é 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 de extrato(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

"is private within this context" numa função de fora

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.

"‘class Conta’ has no member named ‘auditar’"
$ 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);.

"invalid use of non-static data member ‘Fracao::num_’"
    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_.

A filha da classe amiga não tem acesso

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 é membroSem this; chamada como f(x), nunca x.f()
Não é recíproca, herdada nem transitivaCada 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:

void boletim(const Aluno& a);

2. Classe amiga

Complete para a classe Professor acessar tudo da classe Turma (a linha fica dentro da Turma):

friend

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 membros private e protected.
  • Quem concede é sempre a classe. A seção (public/private) onde o friend aparece não importa.
  • Função amiga não é método: não tem this, recebe o objeto por parâmetro e é chamada como f(obj).
  • Uso típico: operadores (<<, *, ==) declarados e escritos dentro da classe com friend.
  • friend class Y; libera todos os métodos de Y.
  • 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.