Resolução da Frequência

Programação Orientada para Objectos

IGE e ETI

2º semestre de 2000/2001

ISCTE


A azul (e indentadas uma vez) as respostas às questões.
Questão 1
Assinale com V (Verdadeiro) as expressões que estão correctas e com F (Falso) as que estão incorrectas.

Deve preencher todos os espaços indicados por um sublinhado (___) com V ou F. Qualquer espaço não preenchido será considerado como uma resposta errada.

Em geral as alíneas podem ter zero ou mais respostas correctas.  Cada resposta correctamente assinalada vale 0,5 valores.

Nas alíneas em que apenas uma resposta está correcta (se existirem estão assinaladas no texto), responder com mais ou menos do que um V anula a cotação.  A resposta correcta corresponde à cotação completa.  Qualquer outra resposta corresponde a zero valores.

Considere as seguintes classes:

class B {
  public:
    B();
    virtual ~B();
    virtual void m1() = 0;
};

class DdeB : public B {
  public:
    DdeB();
    virtual void m1();
    virtual void m2(int p);
};

class DdeD: public DdeB {
  public:
    DdeD();
    virtual void m2(int p);
};

1.1  Quais das seguintes instruções estão correctas?

  V    B* b = new DdeB(); b->m1();

É possível criar uma nova instância de DdeB porque (a) a classe tem uma construtor por omissão público e (b) a classe é concreta, pois fornece um método para a operação abstracta void m1() herdada da classe B.  Como a herança é pública, pode-se atribuir o endereço de um objecto da classe derivada a um ponteiro para a classe base.  Finalmente, a operação void m1() é pública, pelo que pode ser invocada fora da classe.

  F    B* b = new B(); b->m1();

Não é possível criar uma nova instância de B porque essa classe é abstracta.  Isto acontece porque declara uma operação abstracta: void m1().

  F    B* b = new DdeD(); b->m2(3);

Semelhante ao primeiro caso.  É possível criar uma nova instância de DdeD porque (a) a classe tem uma construtor por omissão público e (b) a classe é concreta, pois deriva de uma classe concreta, DdeB, e não declara qualquer operação abstracta.  Como ambas as heranças são públicas, pode-se atribuir o endereço de um objecto da classe (duplamente) derivada a um ponteiro para a classe base.  Finalmente, como não existe nenhuma operação void m2(int) declarada na classe B, é inválida a sua invocação através de uma ponteiro para essa classe (se o ponteiro fosse para a classe DdeB não haveria qualquer problema).

[cotação: 1,5]

1.2  Quais das seguintes definições estão correctas?

  F    void DdeD() { cout << "DdeD::DdeD()" << endl; }

Presume-se que esta definição é feita fora da classe DdeD.  O seu nome teria de ser precedido de DdeD, para deixar claro ser um método (construtor) da classe DdeD.  Note-se que não se pode definir no mesmo âmbito uma rotina e uma classe com o mesmo nome!  Finalmente, se a intenção for de facto definir um construtor para a classe, a palavra chave void está a mais, pois por definição os construtores não devolvem nada e por isso não têm tipo de devolução (ou, quando muito, pode-se dizer que devolvem um objecto da classe em causa, pelo que o tipo de devolução seria redundante face ao nome do construtor...).

  F    DdeD() : DdeB(), B() {}

Na lista de inicializadores só se podem invocar directamente os construtores das classes de que derivam directamente.  A razão é evidente: será esses construtores a invocar os construtores das classes mais acima na hierarquia.  

Note-se, no entanto, que existe o conceito de classe base virtual em C++, que permite resolver alguns problemas que podem surgir com heranças múltiplas, e que obrigam à invocação directa do seu construtor em todas as listas de inicializadores de todas as classes derivadas (directas ou indirectas).  Mas mesmo se fosse esse o caso a ordem das inicializações estaria incorrecta.

  V    DdeD() : DdeB() {}

O construtor por omissão invoca na sua lista de inicializadores o construtor por omissão da classe base.  Está correcto, embora seja redundante.  Pode-se eliminar a lista de inicializadores que o C++ invoca o construtor por omissão da classe base.  Pode-se finalmente eliminar completamente o construtor por omissão, pois o C++ fornece um automaticamente que faz exactamente o mesmo.

[cotação: 1,5]

1.3  Dado o seguinte código:

double a = 3.0;
int* b = 0;
float c = 3.14;
int const* d = new int[4];
int* const e = new int[5];

Quais das seguintes instruções estão correctas?

  F    int* resultado = a < 3.5 ? a : 3.5;

Não se pode inicializar um ponteiro com um inteiro: os tipos são incompatíveis.

  F    b = &c;

Não se pode inicializar um ponteiro para um int  com o endereço de um float: os tipos são incompatíveis.

  V    ++d;

O ponteiro não é constante, pelo que se pode alterar.  O que é constante é a variável apontada pelo ponteiro.

  F    ++e;

Caso oposto.  O ponteiro é constante, embora a variável apontada não o seja.

[cotação: 2]

Questão 2

Recorde-se que deve ler atentamente todo o enunciado antes de resolver as questões!

Pretende-se implementar, em C++, uma aplicação para apoio à divulgação de notícias.  Esta aplicação permite aos seus utilizadores consultar e registar notícias.  São mostradas todas as notícias que se encontram na lista gerida por esta aplicação sempre que um utilizador assim o peça.

A aplicação responsabiliza-se pela actualização das notícias disponíveis para consulta, analisando diariamente as condições necessárias à disponibilização de notícias: as notícias que não verifiquem as condições são removidas da lista gerida por esta aplicação.

A definição da classe GestorDeNotícias, seguidamente apresentada, é uma simplificação da classe que gere as notícias na referida aplicação:

class GestorDeNotícias {
  public:
    GestorDeNotícias();
    ~GestorDeNotícias();

    //
Método invocado diariamente para actualizar as notícias:
    void actualiza(Data const& data_actual);

    void registaNotícia(Notícia* notícia);
    void cancelaAcontecimento(int identificação);

    //
O parâmetro nome é o nome do utilizador que respondeu ao anúncio:
    void registaRespostaAAnúncio(string const& nome, Data const& data_da_resposta);

    //
Método invocado quando o utilizador pede a lista de notícias:
    void mostra() const;

  private:
    list<Notícia*> noticias;
};

Cada notícia contém:

Existem dois tipos concretos de notícias: anúncios e divulgação de acontecimentos.

Os anúncios são notícias que requerem uma resposta.  A disponibilização de um anúncio, que no máximo dura todo o intervalo de validade, cessa logo que algum utilizador lhe responde.  Cada anúncio contém o nome do utilizador que lhe respondeu e a data em que tal aconteceu.  Esta informação é enviada a outro sistema antes da remoção do anúncio (esta operação não é da responsabilidade desta aplicação).

A divulgação de acontecimentos é uma notícia que indica a realização de um evento (e.g., um seminário).  Os acontecimentos são noticiados durante todo o intervalo de validade definido.

Os acontecimentos podem ser cancelados.  Nesse caso os acontecimentos continuam a ser noticiados até expirar o prazo de validade do acontecimento alertando, no entanto, para o seu cancelamento.

Considere a existência de uma classe Data onde se encontram todas operações de que necessite para a resolução destas perguntas desta questão.

2.1  Defina as classes Notícia, Anúncio e AcontecimentoNão é necessário nesta alínea definir quaisquer métodos das classes.

Para responder ao enunciado tem de se usar uma solução semelhante à que se segue.  Note-se no entanto que esta modelação de classes não é a mais elegante, visto que a classe base vê-se obrigada a declarar duas operações abstractas (cancela() e registaResposta()) que fazem sentido apenas numa das classes derivadas (a primeira na classe Acontecimento e a segunda na classe Anúncio).  Não avance nunca para este tipo de solução: é preferível repensar totalmente a organização das classes, nomeadamente na constituição da classe GestorDeNotícias.

class Notícia {
  public:
    //
Declaração de construtores e destrutores:
    Notícia(int identificação, string const& título, 
            string const& descrição, string const& fonte,
            Data const& data_de_inicio, Data const& data_de_fim);

    virtual ~Notícia() {}

    //
Declaração de operações inspectoras:
    int identificação() const;
    string const& título() const;
    string const& descrição() const;
    string const& fonte() const;
    Data const& dataDeInicio() const;
    Data const& dataDeFim() const;
    virtual bool disponível(Data const& data_actual) const = 0;
    virtual void mostra() const;

    //
Declaração de operações modificadoras:
    virtual void cancela(Data const& data_cancelamento) = 0;
    virtual void registaResposta(string const& nome_do_respondente,
                                 Data const& data_da_resposta) = 0;

  private:
    int identificação_;
    string título_;
    string descrição_;
    string fonte_;
    Data data_de_inicio;
    Data data_de_fim;
}
;

class Acontecimento : public Notícia {
  public:
    // Construtores e destrutores:
    Acontecimento(int identificação, string const& título, 
                  string const& descrição, string const& fonte,
                  Data const& data_de_inicio, Data const& data_de_fim);

    // Declaração de métodos inspectores:
    virtual bool disponível(Data const& data_actual) const;
    virtual mostra() const;

    // Declaração de operações inspectoras específicas:
    Data const& dataDeCancelamento() const;
    bool cancelado() const;

    // Declaração de métodos modificadores:
   
virtual void cancela(Data const& data_cancelamento);
    virtual void registaResposta(string const& nome_do_respondente,
                                 Data const& data_da_resposta) {}

  private:
    bool cancelado_;
    Data data_de_cancelamento;
};

class Anúncio : public Notícia {
  public:
    // Construtores e destrutores:
    Anúncio(int identificação, string const& título, 
            string const& descrição, string const& fonte,
            Data const& data_de_inicio, Data const& data_de_fim);

    // Declaração de métodos inspectores:
    virtual bool disponível(Data const& data_actual) const;
    virtual mostra() const;

    // Declaração de operações inspectoras específicas:
    Data const& dataDaResposta() const;
    bool respondido() const;
    string const& nomeDoRespondente() const;

    // Declaração de métodos modificadores:
   
virtual void cancela(Data const& data_cancelamento);
    virtual void registaResposta(string const& nome_do_respondente,
                                 Data const& data_da_resposta) {}

  private:
    Data data_da_resposta_;
    bool respondido_;
    string nome_do_respondente;
};

[cotação: 3]
 
2.2  Assuma que a invocação do método void Notícia::mostra() const faz aparecer informação no ecrã com o seguinte aspecto:

Identificação: 1
Título: Prova de kart
Descrição: Informa-se que dia 2001/07/07 decorrerá uma prova de kart no kardódromo de Palmela.
Fonte: Bruno Abreu
Data de registo: 2001/07/02
Data inicial de transmissão: 2001/07/03
Data final de transmissão: 2001/07/06

Implemente o método void Acontecimento::mostra() const, que mostra no ecrã informação relativa a um acontecimento. Caso o acontecimento tenha sido cancelado, a informação enviada para o ecrã tem o seguinte aspecto:

CANCELADO: 2001/07/04
Identificação: 1
Título: Prova de kart
Descrição: Informa-se que dia 2001/07/07 decorrerá uma prova de kart no kardódromo de Palmela.
Fonte: Bruno Abreu
Data de registo: 2001/07/02
Data inicial de transmissão: 2001/07/03
Data final de transmissão: 2001/07/06

Caso o acontecimento não tenha sido cancelado, a informação enviada para o ecrã tem o seguinte aspecto:

Identificação: 1
Título: Prova de kart
Descrição: Informa-se que dia 2001/07/07 decorrerá uma prova de kart no kardódromo de Palmela.
Fonte: Bruno Abreu
Data de registo: 2001/07/02
Data inicial de transmissão: 2001/07/03
Data final de transmissão: 2001/07/06

void Acontecimento::mostra() const {
    if(cancelado())
        cout << " CANCELADO: " << dataDeCancelamento() << endl;
    Notícia::mostra();
}

[cotação: 2] 

2.3  Implemente o destrutor da classe GestorDeNotícias.  Este método é responsável pela destruição de todas as variáveis dinâmicas contidas nesta classe, tenham ou não sido criadas dentro dela (i.e., pelos seus métodos).

GestorDeNotícias::~GestorDeNotícias() {
    typedef list<Notícia*>::iterator iterador;
    for(iterator i = noticias.begin(); i != noticias.end(); ++i)
        delete *i;
}

[cotação: 2]

2.4  Implemente o método void GestorDeNotícias::actualiza(Data const& data_actual).  Este método é responsável por actualizar as notícias disponíveis na aplicação.  Lembre-se que esta aplicação é responsável pela actualização das notícias disponíveis para consulta, analisando diariamente as condições necessárias à disponibilização de notícias: as notícias que não verifiquem as condições são removidas da lista gerida por esta aplicação.  Implemente todos os métodos das classes Notícia, Anúncio e Acontecimento que forem necessários.

void GestorDeNotícias::actualiza(Data const& data_actual) {
    list<Notícia*>::iterator i = noticias.begin();
    while(i != noticias.end())
        if(not (*i)->disponível(data_actual))
            noticias.erase(i++);
        else
            ++i;

}

bool Anúncio::disponível(Data const& data_actual) const {
    return not respondido() and data_inicio <= data_actual and data_actual <= data_fim;
}

bool Acontecimento::disponível(Data const& data_actual) const {
    return data_inicio <= data_actual and data_actual <= data_fim;
}

[cotação: 3]

Questão 3

Implemente o método void ListaDouble::transfereDe(ListaDouble& outra_lista), pertencente à classe ListaDouble, cuja definição é fornecida em anexo.  O método em questão transfere todos os itens de outra_lista para o fim da lista implícita.  A outra_lista fica vazia.  Não use as classes iteradoras na implementação deste método: use manipulações de ponteiros.  Use tão poucas manipulações de ponteiros quanto possíveis.

A implementação da lista baseia-se no conceito de cadeia duplamente ligada com guardas.

inline void ListaDouble::transfereDe(ListaDouble& outra_lista)
{
    assert(cumpreInvariante() and outra_lista.cumpreInvariante());

    /* Se forem a mesma lista transferir é... não fazer nada! 
       Se outra_lista estiver vazia também não é preciso fazer nada: */
    if(this != &outra_lista and not outra_lista.estaVazia()) {
        //
Basta trocar ponteiros!
        elo_final->anterior->seguinte = outra_lista.elo_inicial->seguinte;
        outra_lista.elo_inicial->seguinte->anterior = elo_final->anterior;
        outra_lista.elo_final->anterior->seguinte = elo_final;
        elo_final->anterior = outra_lista.elo_final->anterior;
        outra_lista.elo_inicial->seguinte = outra_lista.elo_final;
        outra_lista.elo_final->anterior = outra_lista.elo_inicial;

        numero_de_itens += outra_lista.numero_de_itens;
        outra_lista.numero_de_itens = 0;
    }

   
assert(cumpreInvariante() and outra_lista.cumpreInvariante());
}

[cotação: 3]
 
Questão 4

Exemplifique sumariamente o lançamento e a captura de uma excepção em C++ e explique para que servem e em que circunstâncias devem ser usadas.

O mecanismo de excepções em C++ destina-se ao controlo e recuperação de circunstâncias excepcionais, normalmente erros associados a recursos externos ao programa (acesso a ficheiros, reserva de memória, etc.) mas também, se necessário, erros de programação.  Uma excepção é lançada quando se verifica um erro.  O lançamento de uma excepção pode ser forçado através de uma instrução throw.  Uma excepção é capturada e tratada no interior de um bloco de tentativa:

try {
   
... // instruções passíveis de originar uma excepção.
} catch(TipoDeExcepção& nome) {
   
... // instruções de tratamento de uma excepção do tipo TipoDeExcepção.
}

Caso uma excepção lançada não seja capturada por um catch adequado, o programa aborta.

Exemplo: 

Suponha-se uma classe Figura que representa o conceito de figura como uma agregação de formas e as classes Erro e ErroAoCarregar, para representar excepções:

class Erro {
};


class ErroAoCarregar : public Erro {
};

class Figura {
  public:
    Figura(istream& entrada);

   
...
    void carrega(istream& entrada);
    void troca(Figura& outra_figura);

    ...

  private:
    list<Forma*> formas;
};

inline Figura::Figura(istream& entrada) {
    /*
Em caso de erro na extracção lança-se explicitamente uma excepção!
       Note-se que as extracções (operador >>) normalmente não lançam excepções,
       assinalando erro ao colocar o canal em estado de erro, que tem de ser
       verificado explicitamente. */
    int numero_de_formas;
    if(not (entrada >> numero_de_formas))
        throw ErroAoCarregar();

    try {
        while(numero_de_formas-- != 0) {
            string tipo_de_forma;
            if(not (entrada >> tipo_de_forma))
                throw ErroAoCarregar();

            if(tipo_de_forma == "Poligono")
                formas.push_back(new Poligono(entrada));
            else if(tipo_de_forma ==
...)
               
...
            else
                // Tipo de figura desconhecido.
                throw ErroAoCarregar();
        }
    } catch(...) {
        //
Captura-se qualquer excepção para evitar fuga de memória.
       
... // código que remove as formas entretanto reservadas.
        throw; //
relança excepção capturada.
    }
}

inline void Figura::carrega(istream& entrada) {
    //
Usa-se o construtor...
    Figura(entrada).troca(*this);
}

inline void Figura::troca(Figura& outra_figura) {
    formas.swap(outra_figura.formas);
}

Suponha-se uma classe Editor com a responsabilidade de criar e editar figuras:

class Editor {
  public:
   
...
    void carrega();
    ...
 
private:
    bool figura_alterada;
    Figura* figura;
};

void Editor::carrega()
{
    string nome =
...; // pedido ao utilizador...
    if(ifstream entrada(nome.c_str())) {
        try {
            Figura* nova_figura = new Figura(entrada);
            if(figura_alterada)
                guarda();
            delete figura;
            figura = nova_figura;
        } catch(Erro& e) {
           
... // Aviso de formato de ficheiro inválido.
        } catch(bad_alloc) {
           
... // Aviso de memória insuficiente.
        } catch(...) {
           
... // Aviso de erro desconhecido.
        }
    } else
       
... // Aviso de impossibilidade de aceder ao ficheiro.
}

[cotação: 2]

Anexo 1

Definição da classe ListaDouble:

#ifndef LISTA_DOUBLE_H
#define LISTA_DOUBLE_H

#include <iostream>

/**
@brief Representa listas de itens do tipo Item.

   
Item é actualmente é um sinónimo de double, mas que pode ser alterado 
    facilmente para o tipo que se entender.  Por convenção, chama-se "frente"
    e "trás" ao primeiro e último item na lista.  Os nomes "primeiro", "último",
    "início" e "fim" são reservados para iteradores.

    @invariant 0 < numero_de_itens. */
class ListaDouble {
  public:
    typedef double Item;

    class Iterador;
    class IteradorConstante;

    ListaDouble();
    ~ListaDouble();

    Item const& frente() const;
    Item const& trás() const;

    int comprimento() const;

    bool estáVazia() const;
    bool estáCheia() const;

    IteradorConstante primeiro() const;
    IteradorConstante último() const;
    IteradorConstante início() const;
    IteradorConstante fim() const;

    IteradorConstante primeiraOcorrênciaDe(Item const& item) const;
    IteradorConstante últimaOcorrênciaDe(Item const& item) const;

    Item& frente();
    Item& trás();

    void põeNaFrente(Item const& novo_item);
    void põeAtrás(Item const& novo_item);

    void insereAntes(Iterador const& iterador, Item const& novo_item);
    void insereAntes(IteradorConstante const& iterador, Item const& novo_item);

    void tiraDaFrente();
    void tiraDeTrás();

    void esvazia();

    void remove(Iterador& iterador);
    void remove(IteradorConstante& iterador);

    void remove(Iterador const& iterador);
    void remove(IteradorConstante const& iterador);

    void transfereDe(ListaDouble& outra_lista);

    ListaDouble& operator += (ListaDouble const& outra_lista);

    Iterador primeiro();
    Iterador último();
    Iterador início();
    Iterador fim();

    Iterador primeiraOcorrênciaDe(Item const& item);
    Iterador últimaOcorrênciaDe(Item const& item);

private:

    bool cumpreInvariante() const;

    void poe(Elo* elo_anterior, Elo* elo_seguinte, Item const& novo_item);
    void tira(Elo* elo_a_tirar);

    struct Elo {
        Elo(Elo* anterior = 0, Elo* seguinte = 0, Item const& item = Item());
        Item item;
        Elo* anterior;
        Elo* seguinte;
    };

    int número_de_itens;

    Elo* elo_inicial;
    Elo* elo_final;

    friend Iterador;
    friend IteradorConstante;
};

/**
@brief Operador de inserção de listas num canal.

    Todos os itens da lista são inseridos por ordem entre parênteses e separados por vírgulas. */
std::ostream& operator << (std::ostream& saída, ListaDouble const& lista);

#include "lista_double_impl.H"

#endif // LISTA_DOUBLE_H