assert.  Pré-condições:
o programador produtor simplifica a vida do programador consumidor. 
Condições objectivo e invariantes (de ciclos ou de classe):
o programador produtor simplifica a sua própria vida.assert durante o teste do programa.assert sem retirar as instruções
do código.core e
sua utilidade.core.!!
Distinguir excepções de erros desde o início
Usar hierarquias separadas existentes no C++ para erros (runtime) e excepções
Sugerir nova instrução de asserção com excepções? Ver propostas Alexandrescu e Boost.
Explicar: Excepções C++ usados para lidar com erros e com casos excepcionais. Casos excepcionais podem ser falhas (e não erros) em recursos externos. O utilizador engana-se (não erra).
asserções abortivas: suicídio brutal (e.g., comboio), sem elegância nem consideração pelos outros.
asserções lançando excepções: permitem harakiri, com elegância e consideração pelos outros (tentar causar o mínimo de danos), além de antes do golpe final ainda ter tempo para contactar Deus pai confessando os seus pecado (enviar para a empresa produtora informação acerca do estado do programa).
!!
Nesta aula vamos falar de tratamento básico de erros. Note-se que este é um assunto complicado em geral. Vamos assumir que estamos a desenvolver uma aplicação "normal". I.e., vamos admitir que uma falha do programa é muito inconveniente mas não é dramática (nem fatal). É o caso, por exemplo, de um processador de texto. Se fosse um programa de controlo de tráfego aéreo, de cálculo de trajectórias de mísseis, de controlo do arrefecimento de uma central nuclear, teríamos de usar estratégias muito diferentes e nada triviais.
Comecemos por identificar as possíveis fontes de erros num programa:
Discutir com eles! Concluir em:
Como deve o programa lidar com erros do utilizador?
Discutir
É garantido que os utilizadores humanos do programa se vão enganar. É inevitável. Com estes erros devemos lidar explicitamente no programa verificando e lidando com os erros onde quer que eles possam ocorrer.
Há outro princípio na programação: o programador
errará.
Há ainda outro princípio na programação:
alguns os erros do programador resistirão a todos os teste e revelar-se-ão
apenas depois de o programa ter sido distribuido ao seu utilizador final...
Como se deve lidar com estes erros?
Discutir. Não é preciso concluir nada. Deixar para depois.
E os recursos externos? Estes são recursos que estão fora do controlo de programador e do programa propriamente ditos. Exemplos:
Como se deve lidar com estes erros?
Discutir. Não é preciso concluir nada. Deixar para depois.
Note-se que os erros têm muitas vezes origem humana. O utilizador do programa é tipicamente um humano. O programador, quer como programador consumidor quer como programador produtor, é também um humano. Muitas vezes é o mesmo humano que desempenha alternada ou simultaneamente papéis diferentes! Um programador fabrica código usando código já escrito, compila e testa o programa, assumindo neste processo os três papéis.
O que temos de descobrir é como se deve lidar em geral com os vários tipos de erros. Como ferramentas temos as instruções de selecção, condicionais ou de iteração da própria linguagem, as asserções e as excepções. Quando é adequado usar cada uma destas ferramentas?
Vamos ver alguns programas de exemplo e estudar os possíveis erros que podem ocorrer no seu contexto.
Por exemplo, suponham um programa simples para cálculo duma raiz quadrada pelo método de Newton:
Explicar vagamente o método e dizer para eles deduzirem a expressão de progressão enquanto passo o programa no quadro. Deixar claro que não é a forma mais eficiente de calcular a raiz quadrada...
Que acontece se o valor dado pelo utilizador for negativo? E se o utilizador pressionar 'a' em vez de um número?
#include <iostream>#include <limits>
using namespace std;
/**Devolve uma aproximação da raiz quadrada devalor.@pre 0 <=
valor.@post |raizDe×raizDe-valor| <= e ×v, onde e (epsilon) é a diferença entre omenordoublemaior que 1 e 1, representando portanto de alguma
forma o limite mínimo de erro alcançável.
e =numeric_limits<double>::epsilon().*/double raizDe(double const valor){
double raiz_anterior = 0.0;double raiz = valor;
while(raiz != raiz_anterior) {
raiz_anterior = raiz;raiz = 0.5 * (raiz_anterior + valor / raiz_anterior);}
return raiz;}
int main(){cout << "Introduza um valor: ";double valor_lido;cin >> valor_lido;cout << "raiz de " << valor_lido << " é "
<< raizDe(valor_lido) << endl;}
Se o valor for negativo a função entra em ciclo infinito!
De quem é o problema?  Do programador, obviamente! 
Ele deve garantir que as pré-condições da função
se verificam.  Mas qual programador?  Quem programou a função? 
Ou quem programou a função main()?  Bom, os dois, mas de formas totalmente
diferentes.
Quem programa a função (o programador produtor) não pode assumir muito acerca de futuras utilizações. Responsabiliza-se pelo valor devolvido pela função apenas se as pré-condições se verificarem. É esse o contrato que estabelece com o programador consumidor da função. Mas pode ser simpático e garantir que, enquanto o programa estiver em teste, a passagem de argumentos que não cumpram a PC levem à terminação do programa com uma mensagem apropriada. Para isso usam-se asserções.
Note-se que o programador da função está a prepará-la para detectar erros de outros programadores, os programadores consumidores (que podem ser a mesma pessoa)! A função não sabe nada acerca do humano que usa o programa! Assim:
Desse modo, o código fornecedor da função contém uma protecção contra erros no código cliente.
#include <cassert>...
double raizDe(double const valor){
assert(0.0 <= valor);
double raiz_anterior = 0.0;double raiz = valor;
while(raiz != raiz_anterior) {
raiz_anterior = raiz;raiz = 0.5 * (raiz_anterior + valor / raiz_anterior);}
return raiz;}...
É importante perceber que a colocação de uma asserção para verificação das pré-condições da função serve para proteger o programador consumidor dessa função dos seus próprios erros. Por outro lado, é possível também o programador produtor proteger-se dos seus próprios erros colocando uma asserção para verificação das condições objectivo da função.
Neste caso a CO é razoavelmente complicada, pelo que não se entra em pormenores:
Desta forma se o programador produtor se enganar ao escrever a função, a asserção falhará e assinalará que a aproximação não é suficientemente boa.
#include <cstdlib>...
double raizDe(double const valor){
assert(0.0 <= valor);
double raiz_anterior = 0.0;double raiz = valor;
while(raiz != raiz_anterior) {
raiz_anterior = raiz;raiz = 0.5 * (raiz_anterior + valor / raiz_anterior);}
assert(abs(raiz * raiz - valor) <=
numeric_limits<double>::epsilon() * valor);
return raiz;}...
As asserções têm uma vantagem adicional: se falharem o programa termina não apenas com uma mensagem de erros mais ou menos inteligível mas também gerando um ficheiro de "core". Este ficheiro contém uma imagem completa do processo no momento em que a asserção falhou. Esta imagem pode ser usada para determinar ou pelo menos para facilitar a determinação das causas para a falha. Para isso basta executar o depurador e dizer-lhe para carregar a imagem do processo. Se o programa se chamasse "raiz", por exemplo, bastava fazer:
A partir deste instante é possível verificar o valor de todas as variáveis no momento da falha. Para isso pode ser útil usar o comando
gdb raizcore core
up, que sobe um nível na hierarquia de chamadas
de funções e procedimentos, até que a função
ou procedimento mostrado seja um dos que estão em desenvolvimento.
Agora, se o utilizador do programa introduzir um valor negativo o programa
aborta com uma mensagem antipática.  Isso é útil
durante o teste do programa.  Mas muito indesejável para o
seu utilizador!  De quem é a culpa?  Do programador produtor de main() e utilizador de
raizDe()!  Ele deveria verificar
se o valor dado pelo utilizador é aceitável!  A asserção
tem como grande utilidade mostrar claramente ao programador de main()
que há algo de errado com o seu código.  Ele vai verificar
este erro durante os testes do programa.  Presumivelmente antes de
entregar o programa ao utilizador final.
Note-se que há aqui três papeis diferentes: o programador produtor
de raizDe(), o programador consumidor de raizDe() e o utilizador do programa!  Durante o desenvolvimento do programa
estes podem ser papeis tomados alternadamente pela mesma pessoa.
A solução passa pois por reconhecer que o utilizador final se pode enganar e escrever o programa à prova desses erros!
Fazer uma nota acerca do estranho ciclo! Explicar que está entre um...
void lêValorNãoNegativoPara(double& valor_lido){while(true) {cout << "Introduza um valor: ";cin >> valor_lido;
if(0.0 <= valor_lido)return;
cout << "O valor tem de ser não negativo!" << endl;}}
int main(){double valor_lido;lêValorNãoNegativoPara(valor_lido);cout << "raiz de " << valor_lido << " é "
<< raizDe(valor_lido) << endl;}
 while e um
 do while.  Não é uma heresia...
Feito isto e testado o programa, ficou garantido que a função raiz será chamada com um valor não negativo. Nesse caso a verificação das asserções já não são necessárias! Logo, pode-se retirá-la do código. Hmmm..... Será boa ideia? Não! O programa ainda pode precisar de acrescentos, melhorias, correcções, e portanto de ser testado de novo! Se isso acontecer dá muito jeito que a asserção lá continue! Como resolver o problema então? É ineficiente estar lá quando o programa está testado mas é conveniente que lá continue porque podemos precisar de testar o programa de novo!
Solução: desligar as asserções.  Como? 
Compilando com a opção -DNDEBUG (no debug).
Para acelerar o teste de uma aplicação é comum
distribuir versões não finais, as chamadas versões
alfa ou beta, a utilizadores seleccionados.  Estas versões
devem ser distribuidas sem desligar as asserções.  É
que se pode pedir aos utilizadores para, em caso de erro, enviarem uma
mensagem de correio electrónico com:
1.  Mensagem de erro gerada (identifica a asserção
falhada).
2.  Ficheiro imagem: permite inspecção do estado
do programa.
3.  Descrição breve dos passos que geraram o erro:
facilita perceber como se atingiu o estado de erro.
Problema resolvido... De certeza? E se o utilizador escrever uma letra? Aí a leitura falha! E pior, o canal fica num estado de erro em que todas as tentativas de leituras posteriores falham também... Fica em ciclo infinito!
Explicar.
Solução:
Mas, e a terceira fonte de erros? Erros devido a recursos externos ao programa? Exemplos? Falta de memória, falta de espaço em disco, ficheiro inexistente, ficheiro com formato errado... Há aqui algo que me cheira a trabalho final......
void ignoraLinha(){cin.clear();
char caractere;do
cin.get(caractere);
while(not cin.fail() and caractere != '\n');}
void lêValorNãoNegativoPara(double& valor_lido){while(true) {cout << "Introduza um valor: ";cin >> valor_lido;
if(not cin.fail() and 0.0 <= valor_lido)
return;
if(cin.fail()) {ignoraLinha();cout << "Tem de ser um número real!" << endl;
} else // valor_lido < 0.0cout << "O valor tem de ser não negativo!" << endl;}}...
!!!!!!!!!Este exemplo baseia-se no trabalho final de 1999/2000. Pode precisar de ser refeito em anos posteriores...
!!!!A parte final, sobre construtor por cópia, clonagem, etc., deveria ser feita antes! Implementar desenha como stub (tradução?) que escreve no ecrã o que deveria desenhar.
!!!!!!!!!!!Ao passar este exemplo para folhas teóricas é conveniente basear-me na hierarquia de formas desenvolvida nos capítulos anteriores. Talvez essa hierarquia de formas deva passar a usar o Slang++....
Suponhamos que queríamos escrever uma classe Polígono
usando o Slang++ e que essa classe deveria estar integrada numa hierarquia
de conceitos encimada pela classe abstracta Forma:
!!!!!!!!!Nas folhas teóricas pôr Slang::.
!!!!!cumpreInvariante!!!!!
A ideia é que um polígono é representado pela sequência dos seus vértices, admitindo-se que cada vértice se liga por uma aresta aos vértices adjacentes na sequência, sendo que o primeiro e o último vértice se consideram adjacentes. Note-se que a posição dos vértices é relativa à origem do polígono, que é uma posição convencional.
class Forma /*simplificada... */ {public://Com origem emorigem:Forma(Posicao const& origem);
//Tem de haver sempre no topo das hierarquias polimórficas:virtual ~Forma() {}
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const = 0;
//Todas as formas podem ser desenhadas no ecrã:virtual desenha(bool seleccionada = false) = 0;
// Devolve origem da forma:Posicao const& origem() const;
//Move a forma estabelecendo uma nova origem:void movePara(Posicao const& nova_origem);
//Move a forma deslocando a sua origem:void moveDe(Posicao const& deslocamento);
private:Posicao origem_;};
class Polígono : public Forma {public://Sem vértices com origem emorigem:Poligono(Posicao const& origem);
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const;
//O verticevtem coordenadas absolutas...void novoVértice(Posicao const& v);
virtual void desenha(bool seleccionado = false);...
private:list<Posicao> vertices;};
Mas pretendíamos mais. Deveria ser possível carregar e guardar polígonos de e em ficheiros... Para isso acrescentaríamos:
carregaDe() para carregar informação
de um canal, descartando a informação anteriormente guardada
no polígono.guardaEm() para guardar a informação
escrevendo num canal.!!!!!carregaDe e guardaEm
class Forma {public://Com origem emorigem:Forma(Posicao const& origem);//Lida de canal:Forma(istream& entrada);
//Tem de haver sempre no topo das hierarquias polimórficas:virtual ~Forma() = 0;
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const = 0;
//Procedimentos para guardar e carregar:virtual void carrega(istream& entrada);virtual void guarda(ostream& saida) const;
//Todas as formas podem ser desenhadas no ecrã:virtual desenha(bool seleccionada = false) = 0;
// Devolve origem da forma:Posicao const& origem() const;
//Move a forma estabelecendo uma nova origem:void movePara(Posicao const& nova_origem);
//Move a forma deslocando a sua origem:void moveDe(Posicao const& deslocamento);
private:Posicao origem_;};
class Polígono : public Forma {public://Sem vértices com origem emorigem:Poligono(Posicao const& origem);
//Lido de canal:Poligono(istream& entrada);
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const;
//Procedimentos para guardar e carregar:virtual void carrega(istream& entrada);virtual void guarda(ostream& saida) const;
virtual void desenha(bool seleccionado = false) const;
//O verticevtem coordenadas absolutas...void novoVértice(Posicao const& v);
private:list<Posicao> vertices;};
Discutir implementação.
inline Forma::Forma(Posicao const& origem): origem_(origem)
{}
inline Forma::Forma(istream& entrada): origem_(entrada)
{//Que acontece se o construtor dePosicaofalhar?}
inline Forma::~Forma(){}
inline void Forma::carrega(istream& entrada)
{origem_.carrega(entrada);//Que acontece se o procedimentoPosicao::carrega()falhar?}
inline void Forma::guarda(ostream& saida)
{origem_.guarda(saida);//Que acontece se o procedimentoPosicao::guarda()falhar?}
inline Posicao const& Forma::origem() const
{return origem_;}
inline void Forma::movePara(Posicao const& nova_origem)
{origem_ = nova_origem;}
inline void Forma::moveDe(Posicao const& deslocamento)
{origem += deslocamento;}
inline Poligono::Poligono(Posicao const& origem): Forma(origem)
{}
Poligono::Poligono(istream& entrada): Forma(entrada)//Que acontece se o construtor deFormafalhar?{int numero_de_vertices;if(!(entrada >> numero_de_vertices))/* Que fazer se a extracção do número de vértices falhar? */;while(numero_de_vertices-- != 0)vertices.push_back(Posicao(entrada));//Que acontece se o construtor dePosicaofalhar?}
inline string const Poligono::nomeDoTipo() const
{return "Poligono";}
Usar idioma do swap!
Para evitar repetições de código pode-se factorizar o código repetido num procedimento privado auxiliar da classe
void Poligono::carrega(istream& entrada){Forma::carrega(entrada);//Que acontece se o procedimentoForma::carrega()falhar?
vertices.clear();
int numero_de_vertices;if(!(entrada >> numero_de_vertices))/* Que fazer se a extracção do número de vértices falhar? */;while(numero_de_vertices-- != 0)vertices.push_back(Posicao(entrada));//Que acontece se o construtor dePosicaofalhar?}
void Poligono::guarda(ostream& saida) const{Forma::guarda(saida);//Que acontece se o procedimentoForma::guarda()falhar?
if(!(saida << vertices.size()))/* Que fazer se a inserção do número de vértices falhar? */;for(list<Posicao>::const_iterator i = vertices.begin(); i != vertices.end(); ++i)i->guarda(saida));//Que acontece se o procedimentoPosicao::guarda()falhar?}
inline void Poligono::novoVertices(Posicao const& v)
{//O verticevtem coordenadas absolutas, pelo que há que lhe retirar a origem do polígono:vertices.push_back(v - origem());}
void Poligono::desenha(bool seleccionado) const{...}
Poligono:
!!!!Não! Com o idioma do swap é inútil!
!!!!!Não repetir tudo!
Problemas: que fazer quando a leitura ou a escrita falham?
class Forma {public://Com origem emorigem:Forma(Posicao const& origem);//Lida de canal:Forma(istream& entrada);
//Tem de haver sempre no topo das hierarquias polimórficas:virtual ~Forma() {}
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const = 0;
//Procedimentos para guardar e carregar:virtual void carrega(istream& entrada);virtual void guarda(ostream& saida) const;
//Todas as formas podem ser desenhadas no ecrã:virtual desenha(bool seleccionada = false) = 0;
// Devolve origem da forma:Posicao const& origem() const;
//Move a forma estabelecendo uma nova origem:void movePara(Posicao const& nova_origem);
//Move a forma deslocando a sua origem:void moveDe(Posicao const& deslocamento);
private:Posicao origem_;};
class Polígono : public Forma {public://Sem vértices com origem emorigem:Poligono(Posicao const& origem);
//Lido de canal:Poligono(istream& entrada);
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const;
//Procedimentos para guardar e carregar:virtual void carrega(istream& entrada);virtual void guarda(ostream& saida) const;
virtual void desenha(bool seleccionado = false) const;
//O verticevtem coordenadas absolutas...void novoVértice(Posicao const& v);
private:list<Posicao> vertices;
void carregaEspecifico(istream& entrada);};
inline Forma::Forma(Posicao const& origem): origem_(origem) {}
inline Forma::Forma(istream& entrada): origem_(entrada) {//Que acontece se o construtor dePosicaofalhar?}
inline void Forma::carrega(istream& entrada) {origem_.carrega(entrada);//Que acontece se o procedimentoPosicao::carrega()falhar?}
inline void Forma::guarda(ostream& guarda) {origem_.guarda(saida);//Que acontece se o procedimentoPosicao::guarda()falhar?}
inline Posicao const& Forma::origem() const {return origem_;}
inline void Forma::movePara(Posicao const& nova_origem) {origem_ = nova_origem;}
inline void Forma::moveDe(Posicao const& deslocamento) {origem += deslocamento;}
inline Poligono::Poligono(Posicao const& origem): Forma(origem) {}
inline Poligono::Poligono(istream& entrada): Forma(entrada) {//Que acontece se o construtor deFormafalhar?carregaEspecifico(entrada);//Que acontece se o procedimentocarregaEspecifico()falhar?}
inline string const Poligono::nomeDoTipo() const {return "Poligono";}
void Poligono::carrega(istream& entrada){Forma::carrega(entrada);//Que acontece se o procedimentoForma::carrega()falhar?
vertices.clear();
carregaEspecifico(entrada);}
void Poligono::guarda(ostream& saida) const{Forma::guarda(saida);//Que acontece se o procedimentoForma::guarda()falhar?
if(!(saida << vertices.size()))/* Que fazer se a inserção do número de vértices falhar? */;for(list<Posicao>::const_iterator i = vertices.begin(); i != vertices.end(); ++i)i->guarda(saida));//Que acontece se o procedimentoPosicao::guarda()falhar?}
void Poligono::carregaEspecifico(istream& entrada){int numero_de_vertices;if(!(entrada >> numero_de_vertices))/* Que fazer se a extracção do número de vértices falhar? */;while(numero_de_vertices-- != 0)vertices.push_back(Posicao(entrada));// Que acontece se o construtor de Posicao falhar?}
inline void Poligono::novoVertices(Posicao const& v) {//O verticevtem coordenadas absolutas, pelo que há que lhe retirar a origem do polígono:vertices.push_back(v - origem());}
void Poligono::desenha(bool seleccionado) const{...}
Em primeiro lugar há que reconhecer que as causas para uma falha são normalmente externas: os recursos usados para leitura e escrita são tipicamente ficheiros. Estes recursos externos não estão totalmente sob o controlo do programador...
Por um lado é claro que se uma leitura de um canal falhar não se pode pedir de novo os dados, pelo simples facto de não se poder assumir que existe um ente inteligente do outro lado do canal: a maior parte das vezes o canal estará ligado a um ficheiro que, se não tiver o formato correcto, dificilmente se auto-corrigirá...
Por outro lado também não é aceitável que o programa simplesmente aborte quando alguma leitura falhar! Se você fosse utilizador de um programa tão temperamental certamente rogaria pragas ao seu programador.
Logo, é necessário que as funções e os procedimentos assinalem os possíveis erros para que o código onde são invocados possa recuperar desses erros. Uma solução clássica mas problemática é fazer os procedimentos devolver um valor booleano verdadeiro se tudo tiver corrido bem e falso no caso contrário... Por exemplo:
Esta solução tem dois graves problemas:inline bool Forma::carrega(istream& entrada) {return origem_.carrega(entrada);//Que acontece se o procedimentoPosicao::carrega()falhar? Devolve-sefalse.}
bool Poligono::carrega(istream& entrada){if(!Forma::carrega(entrada))return false;//Que acontece se o procedimentoForma::carrega()falhar? Devolve-sefalse.
vertices.clear();
return carregaEspecifico(entrada);}
bool Poligono::carregaEspecifico(istream& entrada){int numero_de_vertices;if(!(entrada >> numero_de_vertices))return false;while(numero_de_vertices-- != 0)vertices.push_back(Posicao(entrada));//Opps... Construtores não podem devolver valores...//Que acontece se o construtor dePosicaofalhar?return true;}
A solução ideal é a utilização de excepções. As vantagens são:
core, que pode ser
analisado como indicado acima para verificar as razões que levaram
ao lançamento da excepção.ErroAoGuardar
em caso de erro e os procedimentos para carregar lançam a excepção
ErroAoCarregar. 
Essas classes fazem parte de uma pequena hierarquia de excepções
com o seguinte aspecto:
Assim, deve-se acrescentar código para lançar excepções apropriadas em caso de erro://Esta classe serve de base a uma pequena hierarquia de classes representando excepções.class Erro {public://Construtor da classe.mensagemé uma mensagem explicando a origem da excepção.Erro(std::string const& mensagem): mensagem(mensagem) {}
//Destrutor virtual para poder sofrer derivações...virtual ~Erro() {}
/*Inspector da mensagem explicando a origem da excepção na forma de uma conversão implícitaparastd::string.*/virtual operator std::string () const {return mensagem;}
private://A mensagem explicando a origem da excepção.std::string mensagem;};
//Esta classe serve para representar excepções de carregamento de objectos a partir de canais.class ErroAoCarregar : public Erro {public://Construtor da classe.classeé o nome da classe que originou a excepção.ErroAoCarregar(std::string const& classe): Erro(std::string("Erro ao carregar '") + classe + "'") {}};
//Esta classe serve para representar excepções ao guardar objectos usando canais.class ErroAoGuardar : public Erro {public://Construtor da classe.classeé o nome da classe que originou a excepção.ErroAoGuardar(std::string const& classe): Erro(std::string("Erro ao guardar '") + classe + "'") {}};
Note-se:class Forma {public://Com origem emorigem:Forma(Posicao const& origem);//Lida de canal:Forma(istream& entrada);
//Tem de haver sempre no topo das hierarquias polimórficas:virtual ~Forma() {}
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const = 0;
//Procedimentos para guardar e carregar:virtual void carrega(istream& entrada);virtual void guarda(ostream& saida) const;
//Todas as formas podem ser desenhadas no ecrã:virtual desenha(bool seleccionada = false) = 0;
// Devolve origem da forma:Posicao const& origem() const;
//Move a forma estabelecendo uma nova origem:void movePara(Posicao const& nova_origem);
//Move a forma deslocando a sua origem:void moveDe(Posicao const& deslocamento);
private:Posicao origem_;};
class Polígono : public Forma {public://Sem vértices com origem emorigem:Poligono(Posicao const& origem);
//Lido de canal:Poligono(istream& entrada);
//Todos os tipos concretos de formas têm um nome:virtual string const nomeDoTipo() const;
//Procedimentos para guardar e carregar:virtual void carrega(istream& entrada);virtual void guarda(ostream& saida) const;
virtual void desenha(bool seleccionado = false) const;
//O verticevtem coordenadas absolutas...void novoVértice(Posicao const& v);
private:list<Posicao> vertices;
void carregaEspecifico(istream& entrada);};
Forma::Forma(Posicao const& origem): origem_(origem) {}
Forma::Forma(istream& entrada): origem_(entrada) {//E se for lançada uma excepção no construtor dePosicao? Deixa-se passar...}
void Forma::carrega(istream& entrada) {origem_.carrega(entrada);//E se for lançada uma excepção no procedimentoPosicao::carrega()? Deixa-se passar...}
void Forma::guarda(ostream& saida) {origem_.guarda(saida);//E se for lançada uma excepção no procedimentoPosicao::guarda()? Deixa-se passar...}
Posicao const& Forma::origem() const {return origem_;}
void Forma::movePara(Posicao const& nova_origem) {origem_ = nova_origem;}
void Forma::moveDe(Posicao const& deslocamento) {origem += deslocamento;}
inline Poligono::Poligono(Posicao const& origem): Forma(origem) {}
Poligono::Poligono(istream& entrada): Forma(entrada)//E se for lançada uma excepção no construtor deForma? Deixa-se passar...{carregaEspecifico(entrada);//E se for lançada uma excepção no procedimentocarregaEspecifico()? Deixa-se passar...}
inline string const Poligono::nomeDoTipo() const {return "Poligono";}
void Poligono::carrega(istream& entrada){Forma::carrega(entrada);//E se for lançada uma excepção no procedimentoForma::carrega()? Deixa-se passar...
vertices.clear();
carregaEspecifico(entrada);//E se for lançada uma excepção no procedimentocarregaEspecifico()? Deixa-se passar...}
void Poligono::guarda(ostream& saida) const{Forma::guarda(saida);//E se for lançada uma excepção no procedimentoForma::guarda()? Deixa-se passar...
//Em caso de erro na inserção lança-se explicitamente uma excepção! Note-se que as//inserçõ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.if(!(saida << vertices.size()))throw ErroAoGuardar("Poligono");
for(list<Posicao>::const_iterator i = vertices.begin();i != vertices.end(); ++i)i->guarda(saida));//E se for lançada uma excepção no procedimentoPosicao::guarda()? Deixa-se passar...}
void Poligono::carregaEspecifico(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_vertices;if(!(entrada >> numero_de_vertices))throw ErroAoCarregar("Poligono");
while(numero_de_vertices-- != 0)vertices.push_back(Posicao(entrada));//E se for lançada uma excepção no construtor dePosicao? Deixa-se passar...}
inline void Poligono::novoVertices(Posicao const& v) {//O verticevtem coordenadas absolutas, pelo que há que lhe retirar a origem do polígono:vertices.push_back(v - origem());}
void Poligono::desenha(bool seleccionado) const{...}
Forma falhar será lançada uma
excepção!  O construtor de Forma limita-se a construir
uma posição.  Essa construção é
que lança a excepção.  O programador da classe
Forma
não tem de se preocupar com o assunto!Forma::carrega() e Forma::guarda().Poligono::guarda()
e Poligono::carregaEspecifico().Figura
que representa o conceito de Figura como uma agregação de
Formas:
Repare-se como se resolveu o problema de carregar e guardar os objectos usando o nome do seu tipo (é o problema da persistência dos objectos).class Figura {public:Figura(istream& entrada);
void carrega(istream& entrada);void guarda(ostream& saida);
...
void desenha() const;
private:list<Forma*> formas;};
inline Figura::Figura(istream& entrada) {carrega(entrada);}
void Figura::carrega(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(!(entrada >> numero_de_formas))throw ErroAoCarregar("Figura");
while(numero_de_formas-- != 0) {string tipo_de_forma;if(!(entrada >> tipo_de_forma))throw ErroAoCarregar("Figura");if(tipo_de_forma == "Poligono")formas.push_back(new Poligono(entrada));else if(tipo_de_forma ==...)...else//Tipo de figura desconhecido. Talvez merecesse uma excepção mais específica...throw ErroAoCarregar("Figura");}//E se for lançada uma excepção no construtor dePoligono? Deixa-se passar...}
void Figura::guarda(ostream& saida) const{//Em caso de erro na inserção lança-se explicitamente uma excepção! Note-se que as//inserçõ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.if(!(saida << formas.size()))throw ErroAoGuardar("Figura");
for(list<Forma*>::const_iterator i = formas.begin(); i != formas.end();++i) {if(!(saida << (*i)->nomeDoTipo()))throw ErroAoGuardar("Figura");(*i)->guarda(saida));//E se for lançada uma excepção no procedimentoForma::guarda()? Deixa-se passar...}}
Suponha-se ainda uma classe Editor, com a responsabilidade
de criar e editar figuras:
class Editor {public:...
void carrega();void guarda() const;
...private:bool figura_alterada;Figura* figura;};
void Editor::carrega(){string nome = CaixaDeTexto("Ficheiro a carregar:").executa();if(ifstream entrada(nome.c_str())) {try {Figura* nova_figura = new Figura(entrada);if(figura_alterada &&MenuSimNao("Quer guardar a figura corrente?").executa())guarda();delete figura;figura = nova_figura;} catch(Erro& e) {Aviso(e).executa();} catch(bad_alloc) {Aviso("Memória insuficiente!").executa();} catch(...) {Aviso("Erro desconhecido ao carregar figura!").executa();}} elseAviso("Não consegui abrir!").executa();}
É claro que a classe Figura não quer lidar
com possíveis erros de carregamento, por exemplo.  Que poderia
ela fazer?  Por outro lado a classe Editor tem obrigação
de fazer alguma coisa.  Essa classe é responsável, presume-se,
pela interação com o utilizador do programa e por isso tem
a responsabilidade de capturar excepções durante o carregamento
de figuras, se elas ocorrerem, e delicadamente avisar o utilizador do programa
e dar-lhe a chance de prosseguir o seu trabalho.
Note-se a estratégia seguida pelo editor: a figura anterior só é descartada quando a nova foi carregada com sucesso. Assim, em caso de falha, o utilizador é avisado e continua a editar a figura anterior.
É importante perceber-se que as excepções podem,
e muitas vezes devem, ser capturadas bastante longe do local onde são
lançadas.  Repare-se no que sucede se ocorrer um erro na leitura
da origem de um polígono da nova figura.  A excepção
é lançada pelo construtor da classe Posicao (do
Slang++), invocado no construtor de Forma, invocado no construtor
de Poligono, invocado no construtor de Figura, invocado
no procedimento carrega() da classe Editor, que finalmente
a captura!
Note-se também que a herança permite a definição
de hierarquias de excepções que podem ser usadas depois para
capturas mais específicas ou mais genéricas.  Neste
caso fez-se a captura de excepções da classe genérica
Erro,
que se presume representarem qualquer possível tipo de erro do Slang++
(note-se a declaração da excepção capturada
como referência de modo a poder fazer uso do polimorfismo). 
Como podem ser lançadas excepções de outros tipos,
nomeadamente bad_alloc se não houver memória ao
criar alguma variável dinâmica, fazem-se capturas em sequência,
chegando-se ao extremo de capturar qualquer excepção que
não seja capturada pelos catch anteriores.
!!!!!!!!!!!!!!!Daqui para baixo são conceitos avançados!
O que se disse até agora é só parte da verdade... É que se uma excepção for lançada a meio de uma operação complexa (e.g., um carregamento) pode haver objectos que fiquem num estado inválido! Isso é muito perigoso! Esta questão tem de ser resolvida pelos programadores produtores. Estes não se podem limitar a lançar excepções em caso de erro e ignorar as excepções com que não queiram lidar... Podem fazê-lo mas apenas se garantirem que todos os objectos ficam num estado aceitável. A isto chama-se "segurança face a excepções" [stroustrup]. Segundo Stroustrup [??] há três níveis de segurança face a excepções que podem ser garantidos pelos programadores produtores de uma dada função ou procedimento membro ou não membro:
É claro que se deve lutar sempre por dar as garantias mais fortes! Mas isso não deve ser feito à custa de uma complexidade exagerada dos programas ou de grandes perdas de eficiência.
Para ajudar neste processo, a linguagem C++ faz algumas garantias. A mais importante delas diz que ao se abandonar uma função ou procedimento devido a uma excepção todos os objectos locais construído são automaticamente destruídos. Claro está que esta garantia significa que se for lançada uma excepção num construtor, o objecto em construção não será deixado semi-construído: todos os atributos (variáveis e constantes membros) entretanto construídos, assim como as classes base, serão automaticamente destruídos. Estas garantias são simpáticas, mas não resolvem todos os problemas. Variáveis dinâmicas não são destruídas automaticamente, por exemplo. Em geral, qualquer recurso externo reservado numa função ou procedimento no qual seja lançada uma excepção (explicita ou implicitamente) devem ser libertados sob risco de haver fugas...
A conclusão a que se chega, portanto, é que a interface de uma função ou procedimento não consiste apenas no seu cabeçalho (que indicam como se usa) e das pré-condição e condição objecto (que indicam o que se compromete a fazer e em que circunstâncias): inclui também informação acerca das garantias de segurança face a excepções (que indicam o que sucede aos objectos envolvidos em casos excepcionais).
Analisemos as garantias que são dadas no código apresentado:
FormaForma, for lançada uma excepção
na construção do atributo origem_, a construção
não tem sucesso.  O bom comportamento da classe Forma
face a erros durante a construção do atributo origem_
depende por isso do bom comportamento dos construtors da classe Posicao.
E durante o procedimento carrega() da classe Forma? 
Como a classe Forma se limita a invocar o procedimento carrega()
da classe Posicao, tudo depende do bom comportamento desse procedimento.
Acontece que quer os construtores da classe Posicao quer o
seu procedimento carrega() oferecem o nível mais forte
de segurança face ao lançamento de excepções
(esta garantia faz parte da interface da classe).  Assim, também
os construtores da classe Forma e o seu procedimento carrega()
oferecem o o nível mais forte de segurança face a excepções.
E o procedimento guarda()?  Aqui o problema é diferente. 
É que guardar não altera senão o canal onde se escreve:
o objecto a guardar nunca é alterado.  Como não é
possível voltar atrás no que se escreveu num canal de saída
genérico, a ideia é que se um procedimento guarda()
falhar, deve-se considerar como lixo tudo o que foi escrito no canal... 
No fundo isto significa que, do ponto de vista do objecto forma, o procedimento
oference o nível forte de segurança, mas do ponto de vista
do canal não, limitando-se a oferecer o nível básico
de segurança.
Calma...  E o canal de entrada no caso do construtor a partir de
um canal e do procedimento carrega(), ambos da classe Forma? 
Não se pode simplesmente "des-ler" aquilo que já foi lido
(por acaso até pode, mas com limitações).  Por
isso, também essas operações oferecem apenas o nível
básico de segurança relativamente ao canal de entrada...
Cientes das nuances face aos canais de entrada e saída apontadas, daqui em diante, para simplificar a discussão, o estado destes canais deixará de ser tido em conta.
PoligonoPor outro lado o construtor a partir de um canal começa por invocar
o construtor da classe Forma.  Como este oferece o nível
forte de segurança, não há qualquer problema. 
Em seguida é invocado implicitamente o construtor por omissão
da lista de vértices.  Também este construtor oferece
o nível forte de segurança face a excepções. 
Finalmente, já no corpo do construtor, é invocado o procedimento
carregaEspecífico(). 
Se durante a sua execução for lançada uma excepção
toda a construção do Poligono falha, sendo invocados
os destrutores da lista de vértices e da classe base.  Perfeito.
O procedimento carrega() da classe Poligono é
mais complicado.  Começa por invocar o procedimento carrega()
da classe Forma, que oferece o nível máximo de segurança
face a excepções.  O problema é que depois invoca
o procedimento carregaEspecifico(), que pode falhar...  Vamos
admitir que o procedimento carregaEspecífico() se comporta
"decentemente", i.e., ou consegue carregar aquilo que é específico
de um polígono (a lista dos vértices), ou mantém a
lista de vértices como estava.  Neste caso temos um problema
complicado.  É que se o carregaEspecifico() falhar,
só parte do polígono foi carregado, nomeadamente a sua origem,
atra'ves do procedimento Forma::carrega(), ficando a lista de
vértices como estava...  Para que seja oferecido o nível
forte de segurança há que garantir que se consegue voltar
atrás no carregamento.  A solução típica
é:
Ou seja, a solução passou por recorrer novoid Poligono::carrega(istream& entrada){Poligono novo(entrada);*this = novo;}
carrega()
ao construtor da classe.  Como o construtor oferece o nível
forte de segurança, uma de duas coisas podem ocorrer:
Outro problema da solução acima é que a atribuição entre polígonos pode lançar excepções... Porquê? Porque implica fazer uma cópia dos vértices, e pode não haver memória para isso!
Os dois problemas podem-se resolver de uma forma simples.  As listas
da biblioteca padrão do C++ disponibilizam uma forma rápida
de troca de itens: o procedimento membro swap.  Pode-se então
reescrever:
Como a atribuição entre duasvoid Poligono::carrega(istream& entrada){Poligono novo(entrada);//Copia o que é genérico (nunca lança excepção):Forma::operator = (novo);//Troca o que é específico (nunca lança excepção):vertices.swap(novo.vertices);}
Forma não lança
excepções e o mesmo se passa com o procedimento swap(),
esse problema está resolvido.
Também fica resolvido o problema da cópia. Agora as listas são trocadas (sem cópia), o que faz com que a lista dos vértices antigos vá parar ao novo polígono, sendo destruída quando esta variável for destruída, i.e., ao se atingir a chaveta final do procedimento.
FiguraEditor e Figura.
O caso da figura é particularmente interessante. O construtor por cópia limita-se a criar uma nova figura contendo ponteiros para as mesmas formas da figura original. Isso é ou não desejável? Duas figuras são iguais se possuírem as mesmas formas ou se possuírem formas iguais? O que nos interessa é semântica de valor ou de cópia?
Em qualquer dos casos põem-se problemas muito interessantes, que serão abordados mais tarde. ????
!!!!!!!!!Semântica de referência: figuras não podem destruir as suas formas! Que o faz, então? Solução: ponteiros inteligentes com contadores de referências? Ou contadores de referências dentro da classe?
!!!!!!!!!!!!Semântica de valor: como construir cópias de objectos heterogéneos? Quando se tem um ponteiro polimórfico e se constrói uma nova variável dinâmica igual ao objecto apontado por esse ponteiro diz-se que se fez uma clonagem. A solução passa por usar uma função membro virtual chamada clona(), que devolve um clone, e que é implementada apropriadamente por todas as classes da hierarquia:
A estas funções de clonagem também se chama construtores virtuais, por razões óbvias.
class Base { // Abstractapublic:Base(Base const&); //construtor por cópia explícito ou implícito.Base* clona() const = 0; // aclonagem é puramente virtual nas classes abstractas e virtual nas concretas.};
classe Derivada : public Base { // Concretapublic:Derivada(Derivada const&); // idem.virtual Derivada* clona() const {return new Derivada(*this);}};
Curiosamente no padrão Compósito (Composto?), o construtor por cópia terá de invocar os clones das formas!
!!!!!!!!!Atenção ao assunto da persistência! Convinha tratá-lo algures!
!!!!!!!!!!!!!!!!!!!Especificações de excepções: incluir?