B  Curiosidades e fenómenos estranhos

Apresentam-se aqui algumas curiosidades acerca da linguagem C++ e da programação em geral.  Note-se que este apêndice contém código que vai do completamente inútil até ao interessante e potencialmente útil (embora apenas em casos particulares).  Não o leia se não se sentir perfeitamente à vontade com a matéria que consta nos capítulos que a este se referem.  A ordem das curiosidades é razoavelmente arbitrária.

B.1  Inicialização

As duas formas de inicialização do C++ diferem em alguns aspectos.  Compare-se o programa
#include <iostream>

using namespace std;

int main()
{
    int i = 1;
    {
        int i = i;
        cout << i << endl;
    }
}

com este outro
#include <iostream>

using namespace std;

int main()
{
    int i = 1;
    {
        int i(i);
        cout << i << endl;
    }
}

O segundo escreve 1 no ecrã.  O primeiro escreve lixo (só por azar, ou sorte, será 1).  Porquê?

Porque no primeiro a variável mais interior já está definida (embora contendo lixo) quando se escreve = i, pelo que a variável é inicializada com o seu próprio valor (tinha lixo... com lixo fica).  No segundo caso a variável mais interior não está ainda definida quando se escreve (i), pois estes parênteses fazem parte da definição.  Assim, a variável interior é inicializada com o valor da variável mais exterior...

B.1.1  Inicialização de membros

Uma consequência interessante do que se disse atrás é que também se aplica nas listas de construtores das classes.  Por exemplo:
class A {
  public:
    A(int i, int j, int k)
        : i(k), // i membro inicializada com k parâmetro!
          j(j), // j membro inicializada com j parâmetro!
          k(i)  // k membro inicializada com i parâmetro!
    {
    }
  private:
    int i;
    int j;
    int k;
};

B.2  Membros acessíveis "só para leitura"

A linguagem C++ proporciona três diferentes categorias de acesso para os membros de uma classe: públicos, protegidos e privados.  O valor de variável membro pública (a evitar porque viola o princípio do encapsulamento) pode ser usado ou alterado directamente sem quaisquer restrições.  Uma variável membro privada não é acessível de todo do exterior.  Não era simpático que existissem variáveis membro cujo valor estivesse disponível para ser usado directamente mas não pudesse ser alterado excepto pela própria classe.  Note-se que uma constante membro pública não resolve o problema, pois nem a classe a pode alterar!

A solução passa por usar uma referência constante pública para aceder a uma variável membro privada!  Por exemplo, se a variável for do tipo int e se chamar valor:

class A {
public:
    // O construtor faz valor referenciar valor_:
    A(int v)
        : valor(valor_), valor_(v) {
    }

    // Quando se usa este truque é fundamental fornecer um construtor por cópia apropriado.  Porquê?
    // Porque senão a referência da cópia refere-se à variável do original!
    A(A const& a)
        : valor(valor_), valor_(a.valor_) {
    }

    // Da mesma forma tem de se fornecer o operador de atribuição por cópia.  Caso contrário não se
    // poderiam fazer atribuições entre instâncias de classes com o truque.  É que o C++ não pode fornecer
    // o operador de atribuição por cópia automaticamente a classes com membros que sejam referências!
    A& operator = (A const& a) {
        valor_ = a.valor_;
        return *this;
    }

    // A referência valor para a variável valor_ é constante para evitar alterações por entidades
    // externas à classe:
    int const& valor;

    // Um procedimento para atribuir um novo valor à variável.  Só para efeitos de teste:
    void poeValor(int v) {
        valor_ = v;
    }

private:
    // A variável propriamente dita é privada...
    int valor_;
};

Com esta classe é possível escrever o seguinte código:
#include <iostream>

using namespace std;

int main()
{
    A a(10);
    cout << a.valor << endl; // Mostra 10.
    a.valor = 20; // Dá erro!
    a.poeValor(30);
    cout << a.valor << endl; // mostra 30.
    A b = a;
    cout << b.valor << endl; // mostra 30.
    A c(40);
    c = a;
    cout << c.valor << endl; // mostra 30.
}

Quem disse que não havia a categoria "só para leitura" em C++?

B.3  Variáveis virtuais

O princípio do encapsulamento obriga a esconder as variáveis membro de uma classe (fazê-las privadas) e fornecer funções de inspecção para o seu valor se necessário.  Isto é fundamental por um conjunto de razões:
  1. A implementação da classe pode precisar de mudar.  As variáveis membro fazem naturalmente parte da implementação de uma classe.  Uma variável membro pública faz também parte da interface da classe, pelo que uma alteração na implementação pode ter impactos sérios em código que use a classe.
  2. Se o valor contido pela variável tiver de ser calculado de forma diferente em classes derivadas, só com funções de inspecção virtuais se pode especializar essa forma de. cálculo.
Suponha-se, por exemplo, uma hierarquia de classes representando contas.  A classe base da hierarquia representa uma abstração de conta.  Para simplificar considera-se que uma conta permite apenas saber o seu saldo (e possui um destrutor virtual, como todas as classes polimórficas):
class Conta {
  public:
    virtual ~Conta() {}
    virtual double saldo() const = 0;
};
Definem-se agora duas concretizações do conceito.  A primeira é uma conta simples.  Uma conta simples tem um saldo como atributo.  Possui um construtor que inicializa o saldo e sobrepõe uma função de inspecção especializada que se limita a devolver esse saldo (ou seja, fornece um método para a operação abstracta saldo()).  A segunda é um portfolio de contas, i.e., uma "conta de contas".  O portfolio de contas tem um procedimento acrescentaConta() que permite acrescentar uma conta ao portfolio, um destrutor para destruir as suas contas e fornece também um método para a operação virtual saldo():
#include <list>

class ContaSimples : public Conta {
  public:
    ContaSimples(double saldo)
        : saldo_(saldo) {
    }
    virtual double saldo() const {
        return saldo_;
    }
  private:
    double saldo_;
};

class Portfolio : public Conta {
  public:
    ~Portfolio() {
        // Destrói contas do portfolio:
        for(std::list<Conta*>::iterator i = contas.begin();
            i != contas.end(); ++i)
            delete *i;
    }
    void acrescentaConta(Conta* nova_conta) {
        contas.push_back(nova_conta);
    }
    virtual double saldo() const {
        // É a soma dos saldos das contas no portfolio:
        double saldo = 0.0;
        for(std::list<Conta*>::const_iterator i = contas.begin();
            i != contas.end(); ++i)
            saldo += (*i)->saldo();
        return saldo;
    }
  private:
    std::list<Conta*> contas;
};

Estas classes podem ser usadas como se segue:
#include <iostream>

using namespace std;

int main()
{
    // Constrói duas contas simples (ponteiros para Conta!):
    Conta* c1 = new ContaSimples(10);
    Conta* c2 = new ContaSimples(30);

    // Mostra saldos das contas simples:
    cout << c1->saldo() << endl; // mostra 10.
    cout << c2->saldo() << endl; // mostra 30.

    // Constrói uma conta portfolio e acrescenta-lhe as duas contas:
    Portfolio* p = new Portfolio;
    p->acrescentaConta(c1);
    p->acrescentaConta(c2);

    // Guarda endereço da conta portfolio no ponteiro c para Conta:
    Conta* c = p;

    // Mostra saldo da conta portfolio:
    cout << c->saldo() << endl; // mostra 40.
}

Note-se que o posimorfismo é fundamental para que a invocação da operação saldo() leve à execução do método saldo() apropriado à classe do objecto apontado e não à classe do ponteiro!  Isto seria muito difícil de reproduzir se se tivesse colocado uma variável membro para guardar o saldo na classe base, mesmo que fosse privada.  Porquê?  Porque nesse caso a operação saldo() não seria abstracta, estando definida na classe base como devolvendo a valor dessa variável membro.  Por isso o valor dessa variável teria de ser mantido coerente com o saldo da conta.  Isso é fácil de garantir para uma conta simples mas muito mais difícil num portfolio, pois sempre que uma classe num portfolio tiver uma mudança de saldo terá de avisar a conta portfolio desse facto para que esta tenha a oportunidade de actualizar o valor da variável.

Se essa variável fosse pública ter-se-ia a desvantagem adicional de futuras alterações na implementação terem impacto na interface da classe, o que é indesejável.

Logo, não se pode usar uma variável membro pública neste caso para guardar o saldo...

.......................................................................

Ok.  Porquê este discurso todo no apêndice de curiosidades e fenómenos estranhos do C++ e numa secção chamada Variáveis virtuais?  Porque...  é possível ter variáveis membro públicas, sem nenhum dos problemas apontados, e ainda apenas com permissão para leitura (restrição fácil de eliminar e que fica como exercício para o leitor).

O truque passa por definir essa variável membro como sendo de uma classe extra DoubleVirtual (amiga da classe Conta) e que possui:

  1. Um construtor que recebe a instância da conta.
  2. Um operador de conversão implícita para double que invoca o operador virtual de cálculo do saldo.
Adicionalmente, pode-se colocar o operador de cálculo do saldo na parte privada das classes, uma vez que deixa de ser útil directamente para o utilizador das classes.  Finalmente, para evitar conflitos de nomes, este operador passa a ter um nome diferente:
class Conta {
  public:
    class DoubleVirtual {
    public:
        DoubleVirtual(Conta& base)
        : base(base) {
        }
        operator double () const {
            return base.calculaSaldo();
        }
    private:
        // Guarda-se uma referência para a conta a que o saldo diz respeito:
        Conta& base;
    };
    friend DoubleVirtual;
    Conta()
        : saldo(*this) {
    }
    Conta(Const const&)
        : saldo(*this) {
    }
    Conta& operator = (Conta const&) {
        return *this;
    }
    virtual ~Conta() {}
    DoubleVirtual saldo;
  private:
    virtual double calculaSaldo() const = 0;
};

#include <list>

class ContaSimples : public Conta {
  public:
    ContaSimples(double saldo)
        : saldo_(saldo) {
    }
private:
    double saldo_;
    virtual double calculaSaldo() const {
        return saldo_;
    }
};

class Portfolio : public Conta {
  public:
    ~Portfolio() {
        for(std::list<Conta*>::iterator i = contas.begin();
            i != contas.end(); ++i)
            delete *i;
    }
    void acrescentaConta(Conta* nova_conta) {
        contas.push_back(nova_conta);
    }
private:
    std::list<Conta*> contas;
    virtual double calculaSaldo() const {
        double saldo = 0.0;
        for(std::list<Conta*>::const_iterator i = contas.begin();
            i != contas.end(); ++i)
            saldo += (*i)->saldo;
        return saldo;
    }
};

O programa de teste é agora mais simples, pois aparenta aceder directamente a um atributo para obter o saldo das contas:
#include <iostream>

using namespace std;

int main()
{
    // Constroi duas contas simples (ponteiros para Conta!):
    Conta* c1 = new ContaSimples(10);
    Conta* c2 = new ContaSimples(30);

    // Mostra saldos das contas simples:
    cout << c1->saldo << endl;
    cout << c2->saldo << endl;

    // Constroi uma conta portfolio e acrescenta-lhe as duas contas:
    Portfolio* p = new Portfolio;
    p->acrescentaConta(c1);
    p->acrescentaConta(c2);

    // Guarda endereço da conta portfolio no ponteiro c para Conta:
    Conta* c = p;

    // Mostra saldo da conta portfolio:
    cout << c->saldo << endl;
}

Este truque pode ser refinado definindo uma classe modelo TipoVirtual que simplifique a sua utilização mais genérica:
template <typename B, typename T>
class TipoVirtual {
  public:
    TipoVirtual(B& base, T (B::*calcula)() const)
        : base(base), calcula(calcula) {
    }
    operator T () const {
        return (base.*calcula)();
    }
  private:
    // Guarda-se uma referência para a classe a que variável diz respeito:
    B& base;
    // Guarda-se um ponteiro para a função membro que devolve o valor da variável:
    T (B::*calcula)() const;
};

class Conta {
  public:
    Conta()
        : saldo(*this, &calculaSaldo) {
    }
    Conta(Const const&)
        : saldo(*this, &calculaSaldo) {
    }
    Conta& operator = (Conta const&) {
        return *this;
    }
    virtual ~Conta() {}
    friend TipoVirtual<Conta, double>;
    TipoVirtual<Conta, double> saldo;
  private:
    virtual double calculaSaldo() const = 0;
};

// O resto tudo igual.

Com esta classe modelo definida, a utilização de "variáveis virtuais" exige apenas pequenas alterações na hierarquia de classes.  Note-se que:
  1. Nada se perdeu em encapsulamento.  A variável saldo é parte da interface mas não é parte da implementação!
  2. Nada se perdeu em polimorfismo.  Continua a existir uma função para devolução do saldo.  Esta função pode (e deve) ser especializada em classes base.
  3. Mas que sucede quando se copiam instâncias de classes com variáveis virtuais?  Classes com variáveis virtuais têm de definir o construtor por cópia!  Têm também de definir o operator de atribuição por cópia!
  4. É possível construir outra classe modelo para permitir acesso completo, incluindo escritas.  Essa classe modelo fica como exercício!