bad_allocSeja a classe PilhaInt representando pilhas limitadas
de inteiros. O seu código
divide-se por dois ficheiros fonte (de interface e auxiliar de implementação):
pilha_int.H
#ifndef PILHA_INT_H#define PILHA_INT_H
class PilhaInt {public:typedef int Item;
PilhaInt();
int altura() const;
bool estáVazia() const;bool estáCheia() const;Item const& topo() const;
Item& topo();
void põe(Item const& novo_item);void tira();
private:static int const capacidade = 100;Item itens[capacidade];int número_de_itens;};
#include "pilha_int_impl.H"
#endif // PILHA_INT_H
pilha_int_impl.H
#include <cassert>
inline PilhaInt::PilhaInt(): número_de_itens(0) {}
inline int PilhaInt::altura() const {return número_de_itens;}
inline bool PilhaInt::estáVazia() const {return altura() == 0;}
inline bool PilhaInt::estáCheia() const {return altura() == capacidade;}
inline PilhaInt::Item const& PilhaInt::topo() const {assert(not estáVazia());
return itens[número_de_itens - 1];}
inline PilhaInt::Item& PilhaInt::topo() {assert(not estáVazia());
return itens[número_de_itens - 1];}
inline void PilhaInt::põe(Item const& novo_item) {assert(not estáCheia());
itens[número_de_itens++] = novo_item;}
inline void PilhaInt::tira() {assert(not estáVazia());
--número_de_itens;}
Uma possível solução do problema passa por guardar os itens da pilha não numa matriz normal do C++, mas sim numa matriz dinâmica, de modo que se possa ir aumentando a sua dimensão à medida das necessidades.
Na realidade a matriz dinâmica não vai crescer. Sempre
que a capacidade actual da pilha estiver esgotada e for necessário
acrescentar-lhe um
novo item, será construída uma nova matriz dinâmica
de maior dimensão, contendo os mesmos itens que a matriz original, que será usada de aí
em diante. A implementação, portanto, usará
um ponteiro para guardar o endereço do primeiro elemento da matriz
dinâmica, em vez da matriz não dinâmica usada na implementação
original. O atributo número_de_itens manter-se-á,
mas é necessário passar o atributo capacidade de
constante de classe a variável de instância de modo a deixar claro que já
não reflecte a capacidade definitiva de todas as pilhas mas sim a capacidade de
cada pilha específica em cada instante de tempo. Entretanto introduz-se
uma nova constante
membro de classe capacidade_inicial que representa a capacidade inicial
da pilha, ou seja, a dimensão inicial da matriz dinâmica:
class PilhaInt {public:typedef int Item;
PilhaInt();
int altura() const;
bool estáVazia() const;bool estáCheia() const;Item const& topo() const;
Item& topo();
void põe(Item const& novo_item);void tira();
private:static int const capacidade_inicial = 32;
int capacidade;Item* itens;int número_de_itens;};
põe(). O construtor deverá
construir a matriz dinâmica com a dimensão apropriada.
O procedimento põe() terá de aumentar a dimensão
da matriz dinâmica sempre que necessário. Uma vez que
na realidade os aumentos são conseguidos através da construção
de uma nova matriz dinâmica e consequente cópia dos itens
da matriz original para a nova matriz, este processo de reconstrução
é oneroso computacionalmente. É pois conveniente que
a matriz dinâmica aumente a bom ritmo, sem no entanto exagerar, pois
tal conduziria a grandes desperdícios de memória. Pode-se
demonstrar que um bom aumento passa por duplicar o tamanho da matriz.
Dessa forma os aumentos da matriz são esporádicos (acabam por ser
amortizados entre todas as operações de colocação de novos itens
intermédias que não obrigam a aumentos da matriz) e, além
disso, garante-se que a matriz dinâmica está sempre ocupada
a 50% pelo menos (bom, quase sempre). Assim:
Note-se que se referiu que a ocupação da pilha era sempre superior a 50%. Será verdade? Em rigor, não. Em primeiro lugar porque a capacidade inicial é 32, e portanto a ocupação só passará a ser superior a 50% a partir do momento em que estejam pelo menos 17 itens na pilha. Em segundo lugar porque os itens também podem sair da pilha! Claro está que seria possível reduzir o tamanho da matriz dinâmica para metade sempre que se retirasse um item da pilha e a sua ocupação se tornasse inferior ou igual a 50%, ou outro critério semelhante que evitasse possíveis oscilações em torno de dois tamanhos. Mas tal não será feito aqui. Assim a implementação dos restantes métodos da classe mantém-se, com excepção do método
inline PilhaInt::PilhaInt(): capacidade(capacidade_inicial),
itens(new Item[capacidade]),
número_de_itens(0) {}
void PilhaInt::põe(Item const& novo_item){if(número_de_itens == capacidade) {//Como não há espaço, constrói-se uma nova matriz dinâmica com o dobro
//da dimensão, duplicando-se a capacidade da pilha:capacidade *= 2;Item* const novos_itens = new Item[capacidade];Copia-se para a nova matriz os itens que estavam na matriz dos itens
//
//original:for(int i = 0; i != altura(); ++i)novos_itens[i] = itens[i];Destrói-se a matriz dos itens original:
//delete[] itens;A matriz dos itens passa a ser a nova matriz construída (já com os itens
//
//antigos):itens = novos_itens;}//Agora há espaço para o novo item de certeza, pode-se inserir normalmente:itens[número_de_itens++] = novo_item;}
estáCheia(). Este
método torna-se complicado pela simples razão de que, se
a matriz dinâmica estiver ocupada a 100%, não é possível
saber se há espaço para um item adicional sem tentar aumentar
a dimensão da matriz. A forma mais simples de o fazer recorre
a excepções, pelo que para já se adoptará a
mesma solução (errada) que se adoptou no caso das listas:
devolver sempre false. Mais tarde se reimplementará este método
de forma mais apropriada. Assim:
Esta discussão, no entanto, não visa propriamente levar a uma implementação mais eficiente do conceito de pilha. O objectivo é discutir a utilização de memória livre dentro de classes. A construção de variáveis dinâmicas dentro de uma classe e para seu uso exclusivo é frequente. Sendo os princípios básicos da utilização da variáveis dinâmicas que estas variáveis têm de ser destruídas e de preferência pela mesma entidade que as construiu, torna-se evidente que a classe
inline bool PilhaInt::estáCheia() const {return false;}
PilhaInt deverá
responsabilizar-se pela destruição da matriz dinâmica
dos itens.
pilha_int define a classe PilhaInt)?
Em cada passo do ciclo é criada uma pilha com pelo menos 32 itens. No fim de cada passo a pilha é destruída. Mas o que é destruído é o espaço ocupado pela instância da pilha em si (que consiste em duas variáveis inteiras,
#include "pilha_int.H"
...for(int i = 0; i != 100000; ++i) {PilhaInt p;...//operações com a pilha.}
capacidade
e número_de_itens, e num ponteiro, itens). A matriz dinâmica
construída no construtor da classe (ou reconstruída no procedimento
põe())
nunca chega a ser destruída! Isto significa que, depois do
ciclo, existem em memória (e inacessíveis) 100000 matrizes
dinâmicas com pelo menos 32 inteiros cada uma. Se cada inteiro
ocupar 4 octetos (bytes), isso significa 12,8 Moctetos de memória
ocupada e impossível de libertar.
Como evitar esta brutal "fuga" de memória? Se se recordar
que as variáveis, ao serem destruídas, invocam o destrutor
da respectiva classe, é claro que a solução é
definir um destrutor para a classe PilhaInt que proceda à
destruição da matriz dinâmica e consequente libertação
da memória ocupada.
Note-se, a propósito, que o C++ procede à invocação dos destrutores de cada variável membro implicitamente, exista ou não destrutor da classe que as contém. Ou seja, o C++ fornece sempre um destrutor às classes definidas pelo utilizador, desde que estas não forneçam um explicitamente.
Acontece, neste caso, que a matriz dinâmica é apontada
pelo ponteiro itens, e que o destrutor dos ponteiros não
destrói as variáveis apontadas. Assim, a classe deverá
passar a definir explicitamente um destrutor que se encarregue de destruir
a matriz dinâmica dos itens:
que se define simplesmente por
class PilhaInt {public:typedef int Item;
PilhaInt();~PilhaInt();...
};
inline PilhaInt::~PilhaInt() {delete[] itens; //é uma matriz dinâmica, por issodelete[].}
Item para ser um sinónimo
de int* e alterar o nome da classe para PilhaPonteiroInt:
Esta alteração é suficiente. Aliás seria muito má ideia ter a tentação de libertar as variáveis apontadas por esses ponteiros no procedimento membro
typedef int* Item;
tira(), por exemplo,
ou mesmo no destrutor da classe. Ou seja:
Porque é essa destruição má ideia? Imagine-se a seguinte utilização:
inline void PilhaPonteiroInt::tira() {assert(not estáVazia());péssima ideia!delete itens[número_de_itens - 1];//--número_de_itens;}
O que acontece ao tentar retirar o item da pilha? O procedimento tenta destruir uma variável que nem sequer é dinâmica. A variável
PilhaPonteiroInt p;int i;p.põe(&i);p.tira();
i, é local e automática, e não
dinâmica!
Isto é, o que acontece quando se atribui uma pilha a outra? O operador de atribuição por cópia é fornecido automaticamente pelo C++ a todas as classes que não o definam explicitamente. Este operador de atribuição por cópia gerado automaticamente simplesmente copia as variáveis membro uma a uma. Claro está que o operador de atribuição por cópia só é fornecido automaticamente pelo C++ quando tal for possível. Em particular tal nunca acontece se a classe possuir constantes ou referências de instância, ou se possuir atributos de instância pertencentes a classes que não tenham por sua vez operador de atribuição por cópia.
PilhaInt p1;PilhaInt p2;
...p2 = p1; //atribuição por cópia.
O mesmo se passa para o construtor por cópia, que também é fornecido automaticamente pela linguagem (excepto se a classe possuir atributos de instância pertencentes a classes que não tenham por sua vez construtor por cópia). Por exemplo, o que resulta do seguinte código?
Em ambos os casos o resultado é desastroso (mais ainda no primeiro). Porquê? Simplesmente porque a variável membro
PilhaInt p1;PilhaInt p2(p1); //(ouPilhaInt p2 = p1;) construtor por cópia.
itens
da instância p2 passa a conter o mesmo endereço que
a variável membro itens da variável p1,
o que significa que ambas contêm o endereço da mesma matriz
dinâmica. I.e., alterações numa das pilhas
passam a afectar a outra e vice-versa. Uma situação
muito indesejável, sobretudo se se levar em conta que inserir um
item numa das pilhas afectará a matriz dinâmica comum, mas
não as variáveis membro número_de_itens e capacidade
da outra, o que pode ser desastroso. No caso da atribuição
por cópia há um problema adicional: perde-se o ponteiro para
a matriz dinâmica original, e portanto há uma fuga de memória.
Em geral as classes devem ser implementadas usando a chamada semântica de valor. Isto significa que variáveis diferentes devem ser totalmente independentes, podendo no entanto tomar o mesmo valor. Por exemplo, depois de
as duas variáveis continuam perfeitamente independentes embora possuam o mesmo valor. Uma posterior atribuição ou alteração de uma das variáveis não afecta a outra. De igual forma se desejaria que depois de
int i = 5;int j = i;
as duas variáveis continuassem independentes, embora com o mesmo valor (que neste caso significa com o mesmo número de itens e com itens de valor idêntico).
PilhaInt p1;PilhaInt p2;
...p2 = p1; //atribuição por cópia.
No caso das pilhas, no entanto, o comportamento descrito acima é altamente indesejável. É portanto necessário, portanto, definir explicitamente o construtor por cópia e o operador de atribuição por cópia que efectuem as operações com a semântica de valor desejada.
PilhaInt (no caso do construtor por cópia
tem de se usar uma passagem por referência, tipicamente constante,
pois se se usasse uma passagem por valor o próprio construtor por
cópia seria invocado recursivamente para copiar o valor do argumento
para o parâmetro respectivo). Este construtor simplesmente constrói
a matriz dinâmica com a mesma dimensão que a da pilha passada
como argumento e enche-a com cópias dos seus itens, fazendo também
cópias dos outros atributos:
Naturalmente é necessário declarar o construtor por cópia na definição da classe:
PilhaInt::PilhaInt(PilhaInt const& outra_pilha): capacidade(outra_pilha.capacidade),
itens(new Item[capacidade]),
número_de_itens(outra_pilha.número_de_itens) {//Copia itens:for(int i = 0; i != número_de_itens; ++i)itens[i] = outra_pilha.itens[i];}
class PilhaInt {public:typedef int Item;
PilhaInt();PilhaInt(PilhaInt const& outra_pilha); //construtor por cópia....};
itens. Se não
se destruir essa matriz, ela permanecerá na memória depois
da atribuição, constituindo assim uma fuga de memória.
Assim, no caso da atribuição por cópia é necessário
não só fazer a cópia, como aconteceu no caso do construtor
por cópia, mas também libertar a matriz dinâmica pré-existente.
Ou seja, é necessário definir o operador como:
Uma observação atenta do código revela que há um caso para o qual a libertação e subsequente reserva de memória é desnecessária: se ambas as matrizes dinâmicas tiverem o mesmo tamanho. Assim, a função pode-se reescrever como:
PilhaInt& PilhaInt::operator = (PilhaInt const& outra_pilha) {capacidade = outra_pilha.capacidade;delete[] itens;itens = new Item[capacidade];número_de_itens = outra_pilha.número_de_itens;Copia itens:
//for(int i = 0; i != número_de_itens; ++i)itens[i] = outra_pilha.itens[i];
return *this;}
Incidentalmente esta última alteração corrigiu também um erro grave da versão original. A versão original daria resultados dramáticos se o utilizador se lembrasse de escrever:
PilhaInt& PilhaInt::operator = (PilhaInt const& outra_pilha) {
if(capacidade != outra_pilha.capacidade) {capacidade = outra_pilha.capacidade;delete[] itens;
itens = new Item[capacidade];}número_de_itens = outra_pilha.número_de_itens;Copia itens:
//for(int i = 0; i != número_de_itens; ++i)itens[i] = outra_pilha.itens[i];
return *this;}
Tente perceber porquê executando a função passo a passo e lembrando-se que, neste caso particular,
p1 = p1;
capacidade e outra_pilha.capacidade
são a mesma variável (a instância implícita
*this
e a referência outra_pilha dizem respeito à mesma variável
p1)
tal como todas as outras variáveis membro (atenção
ao ponteiro itens!). Em particular verifique o que acontece
durante a cópia dos itens...
Para resolver este problema é típico envolver o corpo
do operador de atribuição por cópia num teste que
verifica se a instância implícita e a referência outra_pilha
a partir da qual a cópia será feita se referem à mesma
variável. Isso faz-se comparando os seus endereços,
pois variáveis diferentes estão sempre em zonas de memória
diferentes, com endereços diferentes. Ou seja, a versão
definitiva da função passa a ser:
Naturalmente é necessário declarar o operador de atribuição por cópia na definição da classe:
PilhaInt& PilhaInt::operator = (PilhaInt const& outra_pilha) {
if(this != &outra_pilha) {
if(capacidade != outra_pilha.capacidade) {capacidade = outra_pilha.capacidade;delete[] itens;
itens = new Item[capacidade];}número_de_itens = outra_pilha.número_de_itens;for(int i = 0; i != número_de_itens; ++i)itens[i] = outra_pilha.itens[i];}
return *this;}
Conclui-se portanto que, sempre que uma classe reserva recursos externos para utilização exclusiva pelas suas instâncias, há que ter cuidados acrescidos na definição dos seus construtores, há que definir explicitamente um destrutor que liberte esses recursos, e há que definir explicitamente versões do construtor por cópia e do operador de atribuição por cópia que implementem semântica de valor. Em particular, o construtor por cópia deverá normalmente reservar os seus próprios recursos externos, o mesmo acontecendo com o operador de atribuição por cópia, que deverá previamente libertar os recursos que a instância já reservava ou, alternativamente, reciclá-los (como se fez no caso das pilhas acima). O operador de atribuição por cópia deverá ainda ser implementado de modo a ter um comportamento apropriado mesmo quando o original e a cópia são o mesmo objecto (ou variável).
class PilhaInt {public:...//Atribuição por cópia:
PilhaInt& operator = (PilhaInt const& outra_pilha);...};
pilha_int.H
#ifndef PILHA_INT_H#define PILHA_INT_H
class PilhaInt {public:typedef int Item;
PilhaInt();PilhaInt(PilhaInt const& outra_pilha);
~PilhaInt();
PilhaInt& operator = (PilhaInt const& outra_pilha);
int altura() const;
bool estáVazia() const;bool estáCheia() const;Item const& topo() const;
Item& topo();
void põe(Item const& novo_item);void tira();
private:static int const capacidade_inicial = 32;
int capacidade;Item* itens;int número_de_itens;};
#include "pilha_int_impl.H"
#endif // PILHA_INT_H
pilha_int_impl.H
#include <cassert>
inline PilhaInt::PilhaInt(): capacidade(capacidade_inicial),
itens(new Item[capacidade]),
número_de_itens(0) {}
inline PilhaInt::~PilhaInt() {delete[] itens;}
inline int PilhaInt::altura() const {return número_de_itens;}
inline bool PilhaInt::estáVazia() const {return altura() == 0;}
inline bool PilhaInt::estáCheia() const {return altura() == capacidade;}
inline PilhaInt::Item const & PilhaInt::topo() const {assert(not estáVazia());
return itens[número_de_itens - 1];}
inline PilhaInt::Item& PilhaInt::topo() {assert(not estáVazia());
return itens[número_de_itens - 1];}
inline void PilhaInt::tira() {assert(not estáVazia());
--número_de_itens;}
pilha_int.C
#include "pilha_int.H"
PilhaInt::PilhaInt(PilhaInt const& outra_pilha): capacidade(outra_pilha.capacidade),
itens(new Item[capacidade]),
número_de_itens(outra_pilha.número_de_itens)
{for(int i = 0; i != número_de_itens; ++i)itens[i] = outra_pilha.itens[i];}
PilhaInt& PilhaInt::operator = (PilhaInt const& outra_pilha)
{
if(this != &outra_pilha) {
if(capacidade != outra_pilha.capacidade) {capacidade = outra_pilha.capacidade;delete[] itens;
itens = new Item[capacidade];}número_de_itens = outra_pilha.número_de_itens;for(int i = 0; i != número_de_itens; ++i)itens[i] = outra_pilha.itens[i];}
return *this;}O ideal seria criar um método privado para aumentar a matriz dinâmica e chamá-lo
/*
noif. Nesse caso o procedimentopõe()passaria ainline.*/void PilhaInt::põe(Item const& novo_item){if(número_de_itens == capacidade) {capacidade *= 2;Item* const novos_itens = new Item[capacidade];for(int i = 0; i != altura(); ++i)novos_itens[i] = itens[i];delete[] itens;itens = novos_itens;}itens[número_de_itens++] = novo_item;}
Existem várias abordagens possíveis para lidar com erros. Até agora já se utilizaram as seguintes:
assert]).cstdlib).
Tem a vantagem de ser flexível, pois permite ao programador consumidor do código lidar com os erros da forma
que lhe parecer mais conveniente. Mas, como normalmente verificar
todos os possíveis erros leva a programas muito complexos ("cheios
de ifs"), os programadores tendem a ignorar os valores devolvidos.
Na prática, portanto, esta é uma não-solução.
A segunda solução é demasiado drástica. É verdade que muitas vezes é preferível abortar o programa a continuar depois de um erro grave. Mas esta solução não deixa qualquer possibilidade ao utilizador programador de lidar com o erro em circunstâncias em que é possível recuperar.
O C++ fornece um mecanismo que tem as vantagens de ambas as soluções: as excepções. Ao ocorrer um erro diz-se que se "lança uma excepção" de um dado tipo. Se o utilizador programador nada tiver feito, o programa aborta com uma mensagem apropriada. Se o utilizador programador tiver preparado o seu código para "capturar a excepção", o programa não aborta, sendo executado código específico, escrito pelo programador utilizador, para lidar com o erro.
Será um valor inválido introduzido pelo utilizador do programa um erro que mereça o lançamento de uma excepção, sendo o programa interactivo? Não. Será um erro violar as pré-condições de uma função ou procedimento, passando argumentos inválidos? Claramente. Deverá nesse caso ser lançada uma excepção? Para já não, embora este assunto seja discutido em pormenor mais tarde. Assim, as excepções ficarão reservadas para já para lidar com erros nos recursos externos de um programa.
Ou, usando classes embutidas:
class PilhaIntMemóriaEsgotada {};
As classes cujas instâncias são usadas como excepção servem mais para distinguir entre tipos de excepções (erros), do que para guardar dados, embora também se possam usar com esse objectivo, servindo os dados para guardar informação pormenorizada sobre o erro que lhes deu origem. Assim, é comum encontrarem-se classes usadas para excepções sem quaisquer membros.
class PilhaInt {public:...class MemóriaEsgotada {};...};
Como se utilizam as excepções?
põe()
da classe PilhaInt quando a pilha estiver na sua capacidade máxima
e não for possível construir a nova matriz dinâmica dos itens. Pode-se
definir o procedimento como se segue:
Ou seja, se a pilha estiver no limite e não for possível construir uma nova matriz dinâmica dos itens é lançada uma excepção que é uma instância da classe
void PilhaInt::põe(Item const& novo_item){if(número_de_itens == capacidade) {Item* const novos_itens = new Item[capacidade * 2];
se a construção falhou faça-sethrow MemóriaEsgotada();
capacidade *= 2;for(int i = 0; i != altura(); ++i)novos_itens[i] = itens[i];delete[] itens;itens = novos_itens;}itens[número_de_itens++] = novo_ item;}
PilhaInt::MemóriaEsgotada.
Note-se bem: uma instância da classe, e não uma classe, daí
a necessidade dos parênteses após o nome da classe, que provocam
a invocação do construtor da classe e portanto a criação
duma nova instância da classe.
Note-se que se a excepção for lançada a pilha fica rigorosamente no estado
em que estava originalmente. Foi por isso que se atrasou a operação de
duplicação do atributo capacidade.
Por vezes os construtores das classes de excepções têm parâmetros que identificam melhor o erro. Nesse caso colocam-se argumentos entre parênteses na instrução de lançamento da excepção.
bad_allocO código acima não está completo. Como saber se uma utilização do
operador new[] teve sucesso? Acontece que, em cado de
insucesso, o operador new lança uma excepção: bad_alloc,
que está definida no ficheiro de interface padrão new (fazer #include
<new>).
Como pretendemos, como programadores produtores da classe, lidar com essa
excepção, i.e., capturá-la, temos de envolver o código onde
a excepção pode ser lançada num bloco de tentativa, dizendo explicitamente o que fazer
quando uma excepção de um determinado tipo é capturada
*:
Curiosamente neste caso captura-se uma excepção simplesmente para a substituir/traduzir por outra.
void PilhaInt::põe(Item const& novo_item){if(número_de_itens == capacidade) {
try {Item* const novos_itens = new Item[capacidade * 2];
capacidade *= 2;for(int i = 0; i != altura(); ++i)novos_itens[i] = itens[i];delete[] itens;itens = novos_itens;} catch(bad_alloc) {
throw MemóriaEsgotada();
}
}itens[número_de_itens++] = novo_ item;}
* Para capturar qualquer tipo de excepção,
usar catch(...).
Nesse caso o método
class PilhaInt {public:...class MemóriaEsgotada {
public:
MemóriaEsgotada(int dimensão_pretentida)
: dimensão_pretendida(dimensão_pretendida) {
}
int dimensãoPretendida() {
return dimensão_pretendida;
}
private:
int dimensão_pretendida;
};...};
põe() seria:
A captura da excepção poderia ser feita como se segue:
void PilhaInt::põe(Item const& novo_item){if(número_de_itens == capacidade) {
try {Item* const novos_itens = new Item[capacidade * 2];
capacidade *= 2;for(int i = 0; i != altura(); ++i)novos_itens[i] = itens[i];delete[] itens;itens = novos_itens;} catch(bad_alloc) {
throw MemóriaEsgotada(capacidade * 2);
}
}itens[número_de_itens++] = novo_ item;}
Note-se que neste caso se passaram argumentos ao construtor da excepção e que portanto o código que lida com erro recebe mais informação, nomeadamente qual a capacidade pretendida para a pilha que conduziu à excepção
#include <iostream>
using namespace std;
#include "pilha_int.H"
int main(){try {PilhaInt p;...
} catch(PilhaInt::MemóriaEsgotada e) {cout << "Estoirou ao tentar aumentar capacidade para "
<< e.dimensãoPretendida()<< "!" << endl;}
}
PilhaInt::MemóriaEsgotada.
Note-se também que para receber a informação da excepção
faz-se a captura da excepção nomeando uma variável
para a conter depois de capturada (neste caso de nome e).
O mesmo bloco de tentativa pode capturar tipos de excepção diferentes. Por exemplo:
Finalmente, todo o corpo de uma função ou procedimento pode consistir num grande bloco de tentativa. Por exemplo:
#include <iostream>
using namespace std;
#include "pilha_int.H"
int main(){try {PilhaInt p;...
} catch(PilhaInt::MemóriaEsgotada e) {cout << "Estoirou ao tentar aumentar capacidade para "
<< e.dimensãoPretendida() << "!" << endl;} catch(...) {
cout << "Ooops... Outra excepção qualquer..." << endl;
}
}
Como é óbvio, um bloco de tentativa pode constar em qualquer rotina ou método (e não apenas em
#include <iostream>
using namespace std;
#include "pilha_int.H"
int main()try {PilhaInt p;...
} catch(PilhaInt::MemóriaEsgotada e) {cout << "Estoirou ao tentar aumentar capacidade para "
<< e.dimensãoPretendida() << "!" << endl;} catch(...) {
cout << "Ooops... Outra excepção qualquer..." << endl;
}
main()).
pilha_int das pilhas com
excepções, a melhorar na aula prática, divide-se pelos ficheiros pilha_int.H,
pilha_int_impl.H e pilha_int.C.
Recomenda-se a leitura do Capítulo 5 de [1].
# Existem 10 exemplares na biblioteca do ISCTE.