Resumo da Aula 14

Sumário

Declaração de classes e amizades (friend)

Em certos casos (não muito frequentes) é necessário que uma rotina ou mesmo uma classe inteira tenha acesso aos membros privados de outra classe.  Nestas circunstâncias é possível à classe onde se encontram esses membros declarar quais as classes ou rotinas às quais é permitido o acesso a todos os seus membros (inclusive os privados).  As classes e rotinas que podem aceder aos membros privados de uma dada classe dizem-se amigas dessa classe.  Este tipo de permissão é dado declarando dentro da classe as classes e rotinas que se pretende que dela sejam amigas e precedendo essas declarações da palavra chave friend.  As declarações de amizade podem ficar em qualquer lugar da classe, pois as categorias de acesso não as afectam.  No entanto, como normalmente as relações de amizade entre classes ou classes e rotinas são pormenores de implementação, é recomendável deixar a sua declaração para a parte privada da classe.

Tal como para as rotinas, também a existência de uma classe pode ser declarada antes de se fazer a sua definição.  A declaração de uma classe contém apenas o cabeçalho, não se especificando os seus membros.

Exemplo:

// Declaração da classe Amiga:
class Amiga;

class Promíscua {
  public:

    ...

  private:
    int variável_privada;

    int função_privada(int);

    ...

    friend class Amiga; // todos os métodos da classe Amiga têm acesso
                        // a todos os membros da classe Promíscua (inclusive
                        // os privados).

    friend void penetra(); // o procedimento penetra() tem acesso a todos os
                           // membros da classe Promíscua (inclusive os privados).
};

...

class Amiga {
  public:

    ...

    void fura(Promíscua& p);

    ...

};

void Amiga::fura(Promíscua& p)
{
    ++p.variável_privada;        // acesso a membro privado da classe Promíscua.
    cout << p.função_privada(2); // invocação de operação privada da classe Promíscua.
}

// Este procedimento é amigo da classe Promíscua!
void penetra()
{

    Promíscua p;
    ++p.variável_privada;        // acesso a um membro privado da classe Promíscua.
    cout << p.função_privada(2); // invocação de operação privada da classe Promíscua.
}

// Este procedimento não é amigo da classe Promíscua!
void melga() 
{

    Promíscua p;
    ++p.variável_privada;        // tentativa de acesso a um membro privado da
                                 // classe Promíscua.
    cout << p.função_privada(2); // tentativa de invocação de método privado
                                 // da classe Promíscua.
}

Este tipo de excepção às restrições de acesso (promiscuidades) deve ser utilizado o menos possível e apenas onde não houver melhores alternativas.

1.1  Regras a cumprir

Deve-se sempre tentar que as rotinas sejam normais (não membros).  Se não o puderem ser, por questões de notação ou, mais importante, por necessidade de acesso aos membros privados de uma classe, façam-se membros (operações) dessa classe.  Só se nenhuma das soluções anteriores for possível é que se pode usar o conceito de rotina amiga (mas antes de o fazer repense no problema que está a resolver: é provável que haja uma solução mais simples sem necessidade de promiscuidades...).

Membros de classe (static)

Por vezes é necessário definir atributos que sejam partilhados por todas as instâncias de uma classe.  Estes atributos partilhados por todas as instâncias da classe, e que por isso se dizem atributos de classe (os atributos "normais" dizem-se atributos de instância, pois cada instância da classe possui uma versão própria desses atributos), declaram-se precedendo a sua declaração usual da palavra chave static.  Estes atributos são apenas declarados dentro da classe, sendo obrigatória a sua definição fora da classe (mas aí sem a palavra chave static).

Da mesma forma é possível definir operações que não precisam de ser invocados através de qualquer instância da classe, i.e., operações sem instância implícita.  Mais uma vez utiliza-se a palavra chave static para esse efeito (de novo apenas na declaração da operação, e nunca na definição do respectivo método).  As invocações destas operações faz-se normalmente usando o operador de resolução de âmbito ::, que deve ser precedido do nome da classe em causa.

Por exemplo, é possível que uma classe contenha um atributo de classe e privado que guarda quantas instâncias dessa classe existem em cada momento, e uma operação de classe que devolva o seu valor:

class Contadora {
  public:
    Contadora();

    ~Contadora();

    // Declaração da operação de classe:
    static int númeroDeInstânciasExistentes();

  private:
    // Declaração do atributo de classe:
    static int número_de_instâncias_existentes;
};

inline Contadora::Contadora()
{

    ++número_de_instâncias_existentes;
}

// Isto é um destrutor!  Tem o efeito inverso de um construtor.
inline Contadora::~Contadora()
{

    --número_de_instâncias_existentes;
}

// Definição do atributo de classe (inicialização com zero):
int Contadora::número_de_instâncias_existentes = 0;

// Definição do método de classe correspondente à operação declarada:
inline int Contadora::númeroDeInstânciasExistentes()
{

    return número_de_instâncias_existentes;
}

#include <iostream>

using namespace std;

int main()
{
    cout << Contadora::númeroDeInstânciasExistentes() << endl;
    Contadora a, b, c;
    {
        Contadora d, e, f;
        cout << Contadora::númeroDeInstânciasExistentes() << endl;
    }
    cout << Contadora::númeroDeInstânciasExistentes() << endl;
}

(Este programa escreve 0, 6 e 3 no ecrã.  Porquê?)

(Quando aprender acerca dos construtores por cópia volte a esta classe e identifique um erro subtil que a classe contém, ou seja, um caso em que a contagem do número de instâncias pode falhar.)

O atributo número_de_instâncias_existentes é partilhado por todas as instâncias da classe.  Quando é construída uma instância da classe Contadora, o construtor incrementa o contador de instâncias existentes, que é decrementado pelo destrutor sempre que uma instância desta classe é destruída.