Aula prática 6

Sumário

Objectivos

Os alunos no final desta aula deverão conhecer:

  1. Os problemas associados a classes que reservam recursos externos.
  2. A regra geral que diz que em classes que reservam recursos externos se devem implementar o destrutor, o construtor por cópia e o operador de atribuição por cópia.
  3. Os mecanismos elementares de lançamento e captura de excepções e sua utilização.
  4. A importância de garantir segurança do código face ao lançamento de excepções.
  5. O idioma usual de implementação do operador de atribuição por cópia à custa do construtor por cópia e de uma operação de troca.
  6. As vantagens desse idioma no que toca à segurança face a excepções.
  7. A generalização deste idioma para outros casos.

Deverão também ser capazes de:

  1. Definir classes reservando recursos externos para seu uso exclusivo usando destrutor, construtor por cópia, atribuição por cópia, e com garantia forte de segurança face ao lançamento de excepções.
  2. Utilizar o idioma de implementação da atribuição por cópia, e sua generalização.
  3. Escrever código C++ com lançamento e captura de excepções.
  4. Escrever código C++ com segurança face ao lançamento de excepções.

Caso os alunos sintam que os objectivos não foram atingidos na totalidade deverão concluir/repetir os exercícios desta aula autonomamente e ou recorrer aos horários de dúvidas.

Resumo

O resumo da matéria abordada nesta aula prática pode ser consultado aqui.

Material de apoio

Os ficheiros relativos a esta aula estão disponíveis no arquivo Aula6.zip.

Exercícios

1.  Melhore a implementação da classe ListaDeDouble com iteradores constantes desenvolvida na Aula 5 (ficheiros lista_de_double.H, lista_de_double_impl.H e lista_de_double.C).  Pode encontrar os ficheiros no directório ~/POO/Aula6.

1.a)  O construtor da classe tem um problema.  Se a construção da guarda final falhar é lançada uma excepção bad_alloc e a guarda inicial nunca é destruída!  Corrija o problema capturando a excepção e relançando-a (para relançar uma excepção basta escrever throw;).  Talvez seja necessário passar os ponteiros para as guardas a variáveis (neste momento são constantes).

1.b)  A construção de uma lista à custa de outra usando o construtor por cópia fornecido implicitamente pela linguagem tem os mesmos problemas graves que para a classe PilhaDeInt discutida no resumo e na aula teórica.  Em particular, a lista construída partilha os seus elos dinâmicos com a que lhe serve de original, o que é claramente incorrecto.  Implemente um construtor por cópia que resolva o problema.  Garanta que todas as variáveis dinâmicas são destruídas se a memória faltar a meio da construção ou se for lançada uma excepção durante a cópia dos itens.  

1.c)  Reimplemente o método ListaDeDouble::poe() de forma a garantir que, em caso de lançamento de uma excepção, a pilha em causa fica não apenas num estado válido, mas exactamente no mesmo estado em que estava imediatamente antes de a operação ser invocada (ou seja, a garantia forte da segurança face a excepções).  

1.d)  A atribuição entre duas listas é impossível se existirem as duas constantes membro de instância da solução original: elo_inicial e elo_final.  Mesmo que se passem essas constantes a variáveis, a atribuição tem os mesmos problemas da construção por cópia.  Implemente um operador de atribuição por cópia que resolva o problema.  Garanta que os elos dinâmicos dos itens na lista original são destruídos.  Não se preocupe para já com as garantias de segurança face a excepções.

Discuta como poderia melhorar o código produzido de modo a fornecer os vários níveis de segurança face a excepções.

1.e)  Melhore o operador de atribuição por cópia de modo a fornecer a garantia forte de segurança face a excepções.  Comece por fornecer a com garantia básica.  Depois forneça a garantia forte.  

1.f)  Defina uma nova operação ListaDeDouble::trocaCom() que troque todos os seus itens com a lista passada como argumento.  Recorra ao procedimento std::swap() (#include <algorithm>). 

1.g)  Reimplemente o operador de atribuição por cópia de modo a usar a nova operação.

1.h)  Usando a mesma ideia, reimplemente o operador de concatenação de listas de modo a fazer uso da operação ListaDeDouble::transfereDe().


2.  Relativamente à versão final do módulo das pilhas apresentado no resumo (ficheiros pilha_int.H, pilha_int_impl.H e pilha_int.C, no directório ~/POO/Aula6):

2.a)  Passe para um método auxiliar o código de alteração da capacidade da pilha.  I.e., acrescente uma operação privada PilhaDeInt::Item* PilhaDeInt::novosItens(int capacidade) que construa e devolva uma matriz dinâmica com a capacidade recebida como argumento e contendo cópias dos itens da pilha.  Adopte e adapte parte do código do método PilhaDeInt::põe().

2.b)  Reimplemente o método PilhaDeInt::põe() e o construtor por cópia à custa do novo método privado que criou.  Deve sempre fornecer a garantia forte de segurança face a excepções.

2.c)  Reescreva o procedimento PilhaDeInt::tira() de modo a reduzir a capacidade da pilha para metade sempre que a ocupação estiver a 25% ou menos.  Não deixe nunca que a capacidade seja reduzida abaixo da capacidade inicial.  Este método não pode falhar pelo simples facto de não se ter conseguido reduzir a capacidade!

2.d)  Forneça a classe de uma operação PilhaDeInt::trocaCom() que troque o conteúdo da pilha com o de outra pilha passada como argumento.  Implemente o operador de atribuição por cópia à custa desta nova operação.  Explique porque é que a nova implementação fornece a garantia forte de segurança face a excepções.