Vous êtes sur la page 1sur 81

Curso de Programao III

Introduo a POO

Linguagem de Programao C++


Autores:
Renato Borges
Andr Clnio

Captulo 1
Qualidade do software
O principal objetivo da Engenharia de Software contribuir para a produo de programas de
qualidade. Esta qualidade, porm, no uma idia simples; mas sim como um conjunto de noes e fatores.
desejvel que os programas produzidos sejam rpidos, confiveis, modulares, estruturados,
modularizados, etc. Esses qualificadores descrevem dois tipos de qualidade:
De um lado, considera-se aspectos como eficincia, facilidade de uso, extensibilidade, etc. Estes
elementos podem ser detectados pelos usurios do sistema. A esses fatores atribui-se a
qualidade externa do sofware.
Por outro lado, existe um conjunto de fatores do programa que s profissionais de computao
podem detectar. Por exemplo, legibilidade, modularidade, etc. A esses fatores atribui-se a
qualidade interna do sofware.
Na realidade, qualidade externa do programa em questo que importa quando de sua utilizao. No
entanto, os elementos de qualidade interna so a chave para a conquista da qualidade externa. Alguns
fatores de qualidade externa so apresentados na prxima seo. A seo posterior trata da qualidade
interna, analisando como a qualidade externa pode ser atingida a partir desta.

Fatores de qualidade externa


Corretude
a condio do programa de produzir respostas adequadas e corretas cumprindo
rigorosamente suas tarefas de acordo com sua especificao.
Obviamente, uma qualidade primordial. Se um sistema no faz o que ele foi proposto a fazer, ento
qualquer outra questo torna-se irrelevante.

Robustez
a capacidade do programa de funcionar mesmo sob condies anormais.
A robustez do programa diz respeito ao que acontece quando do aparecimento de situaes anmalas.
Isto diferente de corretude, que define como o programa se comporta dentro de sua especificao. Na
robustez, o programa deve saber encarar situaes que no foram previstas sem efeitos colaterais
catastrficos. Neste sentido, o termo confiabilidade muito utilizado para robustez; porm denota um
conceito mais amplo que melhor interpretado como que englobando os conceitos de corretude e
robustez.

Extensibilidade
definida como a facilidade com que o programa pode ser adaptado para mudanas em sua
especificao.
Neste aspecto, dois princpios so essenciais:
Simplicidade de design: uma arquitetura simples sempre mais simples de ser adaptada ou
modificada
Descentralizao: quanto mais autnomos so os mdulos de uma arquitetura, maior ser a
probabilidade de que uma alterao implicar na manuteno de um ou poucos mdulos.

Capacidade de reuso
a capacidade do programa de ser reutilizado, totalmente ou em partes, para novas
aplicaes.

A necessidade de reutilizao vem da observao de que muitos elementos dos sistemas seguem
padres especficos. Neste sentido, possvel explorar este aspecto e evitar que a cada sistema produzido
os programadores fiquem reinventando solues de problemas j resolvidos anteriormente.

Compatibilidade
a facilidade com que o programa pode ser combinado com outros.
Esta uma qualidade importante pois os programas no so desenvolvidos stand-alone. Normalmente,
existe uma interao entre diversos programas onde o resultado de um determinado sistema utilizado
como entrada para outro.

Outros aspectos
Os aspectos resumidos acima so aqueles que podem ser beneficiados com a boa utilizao das
tcnicas de orientao por objetos. No entanto, existem outros aspectos que no podem ser esquecidos:
Eficincia: o bom aproveitamento dos recursos computacionais como processadores, memria,
dispositivos de comunicao, etc. Apesar de no explicitamente citado anteriormente, este
um requisito essencial para qualquer produto. A linguagem C++ em particular, por ser to
eficiente quanto C, permite o desenvolvimento de sistemas eficientes.
Portabilidade: a facilidade com que um programa pode ser transferido de uma plataforma
(hardware, sistema operacional, etc.). Este fator depende muito da base sobre a qual o sistema
desenvolvido. A nvel de linguagem, C++ satisfaz este fator por ser uma linguagem
padronizada com implementaes nas mais diversas plataformas.
Facildade de uso: a facilidade de aprendizagem de como o programa funciona, sua operao, etc.

Modularidade - qualidade interna


Alguns fatores apresentados na seo anterior podem ser beneficiados com o uso de orientao por
objetos. So eles: corretude, robustez, extensibilidade, reuso e compatibilidade. Estes refletem as mais
srias questes para o desenvolvimento de programas. A medida que se olha para estes cinco aspectos,
dois subgrupos surgem:
Extensibilidade, reuso e compatibilidade demandam arquiteturas que sejam flexveis, design
descentralizado e a construo de mdulos coerentes conectados por interfaces bem definidas.
Corretude e robustez so favorecidos por tcnicas que suportam o desenvolvimento de sistemas
baseados em uma especificao precisa de requisitos e limitaes.
Da necessidade da construo de arquiteturas de programas que sejam flexveis, surge a questo de
tornar os programas mais modulares.
Uma definio precisa para modularidade dificilmente encontrada. No entanto, esta tcnica no traz
benefcios se os mdulos no forem autonmos, coerentes e organizados em uma estrutura robusta. Ser
utilizado no decorrer deste texto um conjunto de cinco critrios e cinco princpios.

Critrios para modularidade


Seguem abaixo cinco critrios que ajudam a avaliar/construir a modularidade do design:

Decomposio
O critrio de decomposio modular alcanado quando o modelo de design ajuda a decomposio do
problema em diversos outros subproblemas cujas solues podem ser atingidas separadamente.
O mtodo deve ajudar a reduzir a aparente complexidade de problema inicial pela sua decomposio
em um conjunto de problemas menores conectados por uma estrutura simples. De modo geral, o processo
repetitivo: os subproblemas so tambm divididos em problemas menores e assim sucessivamente.
Uma exemplificao deste tipo de design o chamado mtodo top-down. Este mtodo dirige os
desenvolvedores a comear com uma viso mais abstrata do funcionamento do sistema. Esta viso
abstrata vai sendo refinada como um conjunto de passos sucessivos menores e assim por diante at que
seus elementos estejam em um nvel que permita sua implementao. Este processo pode ser modelado
como uma rvore.

Composio
O mtodo que satisfaz o critrio de composio favorece a produo de elementos de programas que
podem ser livremente combinados com os outros de forma a produzir novos sistemas, possivelmente em
um ambiente bem diferente daquele em que cada um destes elementos foi criado.
Enquanto que a decomposio se concentra na diviso do software em elementos menores a partir da
especificao, a composio se estabelece no sentido oposto: agregando elementos de programas que
podem ser aplicados para construo de novos sistemas.
A composio diretamente relacionada com a questo da reutilizao: o objetivo achar maneiras de
desenvolver pedaos de programas que executam tarefas bem definidas e utilizveis em outros contextos.
Este contexto reflete um sonho antigo: transformar o processo de design de programas como elementos
independentes onde programas so construdos pela combinao de elementos existentes.
Um exemplo deste tipo de abordagem a construo de bibliotecas como conjuntos de elementos que
podem ser utilizados em diversos programas (pacotes grficos, bibliotecas numricas, etc.).

Entendimento
Um mtodo que satisfaz o critrio de entendimento ajuda a produo de mdulos que podem ser
separadamente compreeendidos pelos desenvolvedores; no pior caso, o leitor deve ter ateno sobre
poucos mdulos vizinhos. Este critrio especialmente relevante quando se tem em vista o aspecto da
manuteno.
Um contra-exemplo deste tipo de mtodo quando h dependncia sequencial onde um conjunto de
mdulos elaborado de modo que a execuo dos mesmos seja feita em uma ordem determinada. Desta
forma, os mdulos no so entendidos de forma individual mas em conjunto com seus vizinhos.

Continuidade
Um mtodo de design satisfaz a continuidade se uma pequena mudana na especificao do problema
resulta em alteraes em um nico ou poucos mdulos. Tal alterao no tem reflexos na arquitetura geral
do sistema; isto , no relacionamento inter-modular.
Este critrio reflete o problema de extensibilidade do sistema. A continuidade significa que eventuais
mudanas devem afetar os mdulos individualmente da estrutura do sistema e no a estrutura em si.
Um exemplo simples deste tipo de critrio a utilizao de constantes representadas por nomes
simblicos definidos em um nico local. Se o valor deve ser alterado, apenas a definio deve ser
alterada.

Proteo
Um mtodo de design satisfaz a proteo se este prov a arquitetura de isolamento quando da
ocorrncia de condies anmalas em tempo de execuo. Ao aparecimento de situaes anormais, seus
efeitos ficam restritos quele mdulo ou pelo menos se propagar a poucos mdulos vizinhos.
Os erros considerados neste critrio so somente aqueles ocorridos em tempo de execuo como falta
de espao em disco, falhas de hardware, etc. No se considera, neste caso, a correo de erros, mas um
aspecto importante para a modularidade: sua propagao.

Princpios de modularidade
Estabelecidos os critrios de modularidade, alguns princpios surgem e devem ser observados
cuidadosamente para se obter modularidade. O primeiro princpio se relaciona com a notao e os outros
se baseiam no modo de comunicao entre os mdulos.
Seguem abaixo estes princpios:

Lingustica modular
Este princpio expressa que o formalismo utilizado para expressar o design, programas, etc. deve
suportar uma viso modular; isto :
Mdulos devem correspoder s unidades sintticas da linguagem utilizada.
Onde a linguagem utilizada pode ser qualquer linguagem de programao, de design de sistemas, de
especificao, etc.
Este princpio segue de diversos critrios mencionados anteriormente:

Decomposio: Se pretende-se dividir o desenvolvimento do sistema em tarefas separadas, cada


uma destas deve resultar em uma unidade sinttica bem delimitada. (compilveis
separadamente no caso de linguagens de programao)
Composio: S pode-se combinar coisas como unidades bem delimitadas.
Proteo: Somente pode haver controle do efeito de erros se os mdulos esto bem delimitados.

Poucas interfaces
Este princpio restringe o nmero de canais de comunicao entre os mdulos na arquitetura do
sistema. Isto quer dizer que:
Cada mdulo deve se comunicar o mnimo possvel com outros.
A comunicao pode ocorrer das mais diversas formas. Mdulos podem chamar funes de outros,
compartilhar estruturas de dados, etc. Este princpios limita o nmero destes tipos de conexo.

Pequenas interfaces
Este princpio relaciona o tamanho das interfaces e no suas quantidades. Isto quer dizer que:
Se dois mdulos possuem canal de comunicao, estes devem trocar o mnimo de informao
possvel; isto , os canais da comunicao inter-modular devem ser limitados.

Interfaces explcitas
Este um princpio que vai mais adiante do que poucas interfaces e pequenas interfaces. Alm de
impor limitaes no nmero de mdulos que se comunicam e na quantidade de informaes trocadas, h a
imposio de que se explicite claramente esta comunicao. Isto :
Quando da comunicao de dois mdulos A e B, isto deve ser explcito no texto de A, B ou
ambos.
Este princpio segue de diversos critrios mencionados anteriormente:
Decomposio e composio: Se um elemento formado pela composio ou decomposio de
outros, as conexes devem ser bem claras entre eles.
Entendimento: Como entender o funcionamento de um mdulo A se seu comportamento
influenciado por outro mdulo B de maneira no clara?

Ocultao de informao (information hiding)


A aplicao deste princpio assume que todo mdulo conhecido atravs de uma descrio oficial e
explcita; ou melhor sua interface. Pela definio, isto requer que haja uma viso restrita do mdulo.
Toda informao sobre um mdulo deve ser oculta (privada) para outro a no ser que seja
especificamente declarada pblica.
A razo fundamental para este princpio o critrio de continuidade. Se um mdulo precisa ser
alterado, mas de maneira que s haja manuteno de sua parte no pblica e no a interface; ento os
mdulos que utilizam seus recursos (clientes) no precisam ser alterados. Alm, quanto menor a interface,
maiores as chances de que isto ocorra.
Apesar de no haver uma regra absoluta sobre o que deve ser mantido pblico ou no, a idia geral
simples: a interface deve ser uma descrio do funcionamento do mdulo. Tudo que for relacionado com
sua implementao no deve ser pblico.
importante ressaltar que, neste caso, este princpio no implica em proteo no sentido de restries
de segurana. Embora seja invisvel, pode haver acesso s informaes internas do mdulo.

Calculadora RPN em C
Os primeiros exemplos sero feitos a partir de um programa escrito em C. Vrias modificaes sero
feitas at que este se torne um programa C++. O programa uma calculadora RPN (notao polonesa
reversa), apresentada nesta seo.

Este tipo de calculadora utiliza uma pilha para armazenar os seus dados. Esta pilha est implementada
em um mdulo parte. O header file et listado abaixo:
#ifndef stack_h
#define stack_h
#define MAX 50
struct Stack {
int top;
int elems[MAX];
};
void push(struct Stack* s, int i);
int pop(struct Stack* s);
int empty(struct Stack* s);
struct Stack* createStack(void);
#endif
A implementao destas funes est no arquivo stack-c.c:
#include <stdlib.h>
#include "stack-c.h"
void push(struct Stack*s, int i) { s->elems[s->top++] = i; }
int pop(struct Stack*s)
{ return s->elems[--(s->top)]; }
int empty(struct Stack*s)
{ return s->top == 0; }
struct Stack* createStack(void)
{
struct Stack* s = (struct Stack*)malloc(sizeof(struct Stack));
s->top = 0;
return s;
}
A calculadora propriamente dita utiliza estes arquivos:
#include <stdlib.h>
#include <stdio.h>
#include "stack-c.h"
/* dada uma pilha, esta funo pe nos
parmetros n1 e n2 os valores do topo
da pilha. Caso a pilha tenha menos de dois
valores na pilha, um erro retornado */
int getop(struct Stack* s, int* n1, int* n2)
{
if (empty(s))
{
printf("empty stack!\n");
return 0;
}
*n2 = pop(s);
if (empty(s))
{
push(s, *n2);
printf("two operands needed!\n");
return 0;
}
*n1 = pop(s);
return 1;
}

/* a funo main fica em um loop


lendo do teclado os comandos da calculadora.
Se for um nmero, apenas empilha.
Se for um operador, faz o calculo.
Se for o caracter 'q', termina a execuo.
Aps cada passo a pilha mostrada. */
int main(void)
{
struct Stack* s = createStack();
while (1)
{
char str[31];
int i;
printf("> ");
gets(str);
if (sscanf(str, "%d", &i)==1) push(s, i);
else
{
int n1, n2;
char c;
sscanf(str, "%c", &c);
switch(c)
{
case '+':
if (getop(s, &n1, &n2)) push(s, n1+n2);
break;
case '-':
if (getop(s, &n1, &n2)) push(s, n1-n2);
break;
case '/':
if (getop(s, &n1, &n2)) push(s, n1/n2);
break;
case '*':
if (getop(s, &n1, &n2)) push(s, n1*n2);
break;
case 'q':
return 0;
default:
printf("error\n");
}
}
{
int i;
for (i=0; i<s->top; i++)
printf("%d:%6d\n", i, s->elems[i]);
}
}
}

Tipos abstratos de dados


O conceito de tipo foi um passo importante no sentido de atingir uma linguagem capaz de suportar
programao estruturada. Porm, at agora, no possvel usar uma metodologia na qual os programas
sejam desenvolvidos por meio de decomposies de problemas baseadas no reconhecimento de
abstraes. Para a abstrao de dados adequada a este propsito no basta meramente classificar os
objetos quanto a suas estruturas de representao; em vez disso, os objetos devem ser classificados de
acordo com o seu comportamento esperado. Esse comportamento expressado em termos das operaes
que fazem sentido sobre esses dados; estas operaes so o nico meio de criar, modificar e se ter acesso
aos objetos.

Classes em C++
Uma classe em C++ o elemento bsico sobre o qual toda orientao por objetos est apoiada. Em
primeira instncia, uma classe uma extenso de uma estrutura, que passa a ter no apenas dados, mas
tambm funes. A idia que tipos abstratos de dados no so definidos pela sua representao interna,
e sim pelas operaes sobre o tipo. Ento no h nada mais natural do que incorporar estas operaes no
prprio tipo.Estas operaes s fazem sentido quando associadas s suas representaes.
No exemplo da calculadora, um candidato natural a se tornar uma classe a pilha. Esta uma
estrutura bem definida; no seu header file esto tanto a sua representao (struct Stack) quanto as funes
para a manipulao desta representao. Seguindo a idia de classe, estas funes no deveriam ser
globais, mas sim pertencerem estrutura Stack. A funo empty seria uma das que passariam para a
estrutura. A implementao de empty pode ficar dentro da prpria estrutura:
struct Stack {
// ...
int empty() { return top == 0; }
};
ou fora:
struct Stack {
// ...
int empty();
};
int Stack::empty(void) { return top == 0; }
No segundo caso a declarao fica no header file e a implementao no .c. A diferena entre as duas
opes ser explicada na seo sobre funes inline.
Com esta declarao, as funes so chamadas diretamente sobre a varivel que contm a
representao:
void main(void)
{
struct Stack s;
s.empty();
}
Repare que a funo deixou de ter como parmetro uma pilha. Isto era necessrio porque a funo
estava isolada da representao. Agora no, a funo faz parte da estrutura. Automaticamente todos os
campos da estrutura passam a ser visveis dentro da implementao da funo, sem necessidade se
especificar de qual pilha o campo top deve ser testado (caso da funo empty). Isto j foi dito na chamada
da funo.

O paradigma da orientao a objetos


Esta seo apresenta cinco componentes chave do paradigma de orientao por objetos. As
componentes so objeto, mensagem, classe, instncia e mtodo. Eis as definies:
Objeto: uma abstrao encapsulada que tem um estado interno dado por uma lista de atributos
cujos valores so nicos para o objeto. O objeto tambm conhece uma lista de mensagens que
ele pode responder e sabe como responder cada uma. Encapsulamento e abstrao so
definidos mais frente.
Mensagem: representada por um identificador que implica em uma ao a ser tomada pelo objeto
que a recebe. Mensagens podem ser simples ou podem incluir parmetros que afetam como o
objeto vai responder mensagem. A resposta tambm influenciada pelo estado interno do
objeto.
Classe: um modelo para a criao de um objeto. Inclui em sua descrio um nome para o tipo de
objeto, uma lista de atributos (e seus tipos) e uma lista de mensagens com os mtodos
correspondentes que o objeto desta classe sabe responder.
Instncia: um objeto que tem suas propriedades definidas na descrio da classe. As propriedades
que so nicas para as instncias so os valores dos atributos.
Mtodo: uma lista de instrues que define como um objeto responde a uma mensagem em
particular. Um mtodo tipicamente consiste de expresses que enviam mais mensagens para
objetos. Toda mensagem em uma classe tem um mtodo correspondente.

Traduzindo estas definies para o C++, classes so estruturas, objetos so variveis do tipo de
alguma classe (instncia de alguma classe), mtodos so funes de classes e enviar uma mensagem para
um objeto chamar um mtodo de um objeto.
Resumindo:
Objetos so instncias de classes que respondem a mensagens de acordo com os mtodos e
atributos, descritos na classe.

Exerccio 1 - Calculadora RPN com classes


Transformar a pilha utilizada pela calculadora em uma classe.

Captulo 2a
Descrio de recursos de C++ no relacionados s classes
Comentrios
O primeiro recurso apresentado simplesmente uma forma alternativa de comentar o cdigo. Em
C++, os caracteres // iniciam um comentrio que termina no fim da linha na qual esto estes caracteres.
Por exemplo:
int main(void)
{
return -1; // retorna o valor -1 para o sistema operacional
}

Declarao de variveis
Em C as variveis s podem ser declaradas no incio de um bloco. Se for necessrio usar uma varivel
no meio de uma funo existem duas solues: voltar ao incio da funo para declarar a varivel ou abrir
um novo bloco, simplesmente para possibilitar uma nova declarao de varivel.
C++ no tem esta limitao, e as variveis podem ser declaradas em qualquer ponto do cdigo:
void main(void)
{
int a;
a = 1;
printf("%d\n", a);
// ...
char b[] = "teste";
printf("%s\n", b);
}
O objetivo deste recurso minimizar declaraes de variveis no inicializadas. Se a varivel pode
ser declarada em qualquer ponto, ela pode ser sempre inicializada na prpria declarao. Um exemplo
comum o de variveis usadas apenas para controle de loops:
for (int i=0; i<20; i++) // ...
A varivel i est sendo criada dentro do for, que o ponto onde se sabe o seu valor inicial. O escopo
de uma varivel desde a sua declarao at o fim do bloco corrente (fecha chaves). Na linha acima a
varivel i continua existindo depois do for, at o fim do bloco.
interessante notar que esta caracterstica diferente de se abrir um novo bloco a cada declarao.
Uma tentativa de declarar duas variveis no mesmo bloco com o mesmo nome causa um erro, o que no
acontece se um novo bloco for aberto.

Declarao de tipos
Em C++, as declaraes abaixo so equivalentes:
struct a {
// ...
};
typedef struct a {
// ...
} a;
Ou seja, no mais necessrio o uso de typedef neste caso. A simples declarao de uma estrutura j
permite que se use o nome sem a necessidade da palavra reservada struct , como mostrado abaixo:
struct a {};
void f(void)
{

struct a a1;
a
a2;

// em C o tipo usado como "struct a"


// em C++ a palavra struct
// no mais necessria

Declarao de unies
Em C++ as unies podem ser annimas. Uma unio annima uma declarao da forma:
union { lista dos campos };
Neste caso, os campos da unio so usados como se fossem declarados no escopo da prpria unio:
void f(void)
{
union { int a; char *str; };
a = 1;
// ...
str = malloc( 10 * sizeof(char) );
}
a e str so campos de uma unio annima, e so usados como se tivessem sido declarados como
variveis da funo. Mas na realidade as duas tem o mesmo endereo. Um uso mais comum para unies
annimas dentro de estruturas:
struct A {
int tipo;
union {
int
inteiro;
float real;
void *ponteiro;
};
};
Os trs campos da unio podem ser acessados diretamente, da mesma maneira que o campo tipo.

Prottipos de funes
Em C++ uma funo s pode ser usada se esta j foi declarada. Em C, o uso de uma funo no
declarada geralmente causava uma warning do compilador, mas no um erro. Em C++ isto um erro.
Para usar uma funo que no tenha sido definida antes da chamada tipicamente chamada de
funes entre mdulos , necessrio usar prottipos. Os prottipos de C++ incluem no s o tipo de
retorno da funo, mas tambm os tipos dos parmetros:
void f(int a, float b); // prottipo da funo f
void main(void)
{
f(1, 4.5); // o prottipo possibilita a utilizao de f aqui
}
void f(int a, float b)
{
printf("%.2f\n", b+a+0.5);
}
Uma tentativa de utilizar uma funo no declarada gera um erro de smbolo desconhecido.

Funes que no recebem parmetros


Em C puro, um prottipo pode especificar apenas o tipo de retorno de uma funo, sem dizer nada
sobre seus parmetros. Por exemplo,
float f(); // em C, no diz nada sobre os parmetros de f
um prottipo incompleto da funo f. De acordo com a seo anterior, um prottipo incompleto no
faz muito sentido. Na realidade, esta uma das diferenas entre C e C++. Um compilador de C++

interpretar a linha acima como o prottipo de uma funo que retorna um float e no recebe nenhum
parmetro. Ou seja, exatamente equivalente a uma funo (void):
float f(); // em C++ o mesmo que float f(void);

Funes inline
Funes inline so comuns em C++, tanto em funes globais como em mtodos. Estas funes tem
como objetivo tornar mais eficiente, em relao velocidade, o cdigo que chama estas funes. Elas so
tratadas pelo compilador quase como uma macro: a chamada da funo substituda pelo corpo da
funo. Para funes pequenas, isto extremamente eficiente, j que evita gerao de cdigo para a
chamada e o retorno da funo.
O corpo de funes inline pode ser to complexo quanto uma funo normal, no h limitao
alguma. Na realidade o fato de uma funo ser inline ou no apenas uma otimizao; a semntica de
uma funo e sua chamada a mesma seja ela inline ou no.
Este tipo de otimizao normalmente s utilizado em funes pequenas, pois funes inline grandes
podem aumentar muito o tamanho do cdigo gerado. Do ponto de vista de quem chama a funo, no faz
nenhuma diferena se a funo inline ou no.
Nem todas as funes declaradas inline so expandidas como tal. Se o compilador julgar que a funo
muito grande ou complexa, ela tratada como uma funo normal. No caso de o programador
especificar uma funo como inline e o compilador decidir que ela ser uma funo normal, ser
sinalizada uma warning. O critrio de quando expandir ou no funes inline no definido pela
linguagem C++, e pode variar de compilador para compilador.
Para a declarao de funes inline foi criada mais uma palavra reservada, inline. Esta deve preceder a
declarao de uma funo inline:
inline double quadrado(double x)
{
return x * x;
}
Neste caso a funo quadrado foi declarada como inline. A chamada desta funo feita
normalmente:
{
double c = quadrado( 7 );
double d = quadrado( c );
}
Assim como a chamada feita da mesma maneira, o significado tambm o mesmo com ou sem
inline. A diferena fica por conta do cdigo gerado. O trecho de cdigo acima ser compilado como se
fosse:
{
double c = 7 * 7;
double d = c * c;
}
Alm de ser mais eficiente, possibilita, por parte do compilador, otimizaes extras. Por exemplo,
calcular automaticamente, durante a compilao, o resultado de 7*7, gerando cdigo para uma simples
atribuio de 49 a c.
Fazendo uma comparao com as macros, que so usadas em C para produzir este efeito, nota-se que
funes inline as substituem com vantagens. A primeira que uma macro uma simples substituio de
texto, enquanto que funes inline so elementos da linguagem, e so verificadas quanto a erros. Alm
disso, macros no podem ser usadas exatamente como funes, como mostra o exemplo a seguir:
#define quadrado(x) ((x)*(x))
void main(void)
{
double a = 4;
double b = quadrado(a++);
}
Antes da compilao, a macro ser expandida para:
double b = ((a++)*(a++));

A execuo desta linha resultar na atribuio de 4*5 = 20 a b, alm de incrementar duas vezes a
varivel a. Com certeza no era este o efeito desejado.
Assim como funes, mtodos tambm podem ser declarados como inline. No caso de mtodos no
necessrio usar a palavra reservada inline. A regra a seguinte: funes com o corpo declarado dentro da
prpria classe so tratadas como inline; funes declaradas na classe mas definidas fora no so inline.
Supondo uma classe Pilha, o mtodo vazia simples o suficiente para justificar uma funo inline:
struct Pilha {
elemPilha* topo;
int vazia() { return topo==NULL; }
void push(int v);
int pop();
};
Alguns detalhes devem ser levados em considerao durante a utilizao de funes inline. O que o
compilador faz quando se depara com uma chamada de funo inline? O corpo da funo deve ser
expandido no lugar da chamada. Isto significa que o corpo da funo deve estar disponvel para o
compilador antes da chamada.
Um mdulo C++ composto por dois arquivos, o arquivo com as declaraes exportadas (.h) e outro
com as implementaes (.c, .cc ou .cpp). Tipicamente, uma funo exportada por um mdulo tem o seu
prottipo no .h e sua implementao no .c. Isso porque um mdulo no precisa conhecer a implementao
de uma funo para us-la. Mas isso s verdade para funes no inline. Por causa disso, funes inline
exportadas precisam ser implementadas no .h e no mais no .c.

Referncias
Referncia um novo modificador que permite a criao de novos tipos derivados. Assim como podese criar um tipo ponteiro para um inteiro, pode-se criar uma referncia para um inteiro. A declarao de
uma referncia anloga de um ponteiro, usando o caracter & no lugar de *.
Uma referncia para um objeto qualquer , internamente, um ponteiro para o objeto. Mas,
diferentemente de ponteiros, uma varivel que uma referncia utilizada como se fosse o prprio
objeto. Os exemplos deixaro estas idias mais claras. Vamos analisar referncias em trs utilizaes:
como variveis locais, como tipos de parmetros e como tipo de retorno de funes.
Uma varivel local que seja uma referncia deve ser sempre inicializada; a no inicializao causa um
erro de compilao. Como as referncias se referenciam a objetos, a inicializao no pode ser feita com
valores constantes:
{
int
int&
int&
int&

a;
b = a;
c;
d = 12;

//
//
//
//

ok, varivel normal


ok, b uma referencia para a
erro! no foi inicializada
erro! inicializao invlida

}
A varivel b utilizada como se fosse realmente um inteiro, no h diferena pelo fato de ela ser uma
referncia. S que b no um novo inteiro, e sim uma referncia para o inteiro guardado em a. Qualquer
alterao em a se reflete em b e vice versa. como se b fosse um novo nome para a mesma varivel a:
{
int a = 10;
int& b = a;
printf("a=%d, b=%d\n", a, b); // produz a=10, b=10
a = 3;
printf("a=%d, b=%d\n", a, b); // produz a=3, b=3
b = 7;
printf("a=%d, b=%d\n", a, b); // produz a=7, b=7
}
No caso de a referncia ser um tipo de algum argumento de funo, o parmetro ser passado por
referncia, algo que no existia em C e era simulado passando-se ponteiros:
void f(int a1, int &a2, int *a3)
{
a1 = 1; // altera cpia local
a2 = 2; // altera a varivel passada (b2 de main)

*a3 = 3;

// altera o contedo do endereo de b3

}
void main()
{
int b1 = 10, b2 = 20, b3 = 30;
f(b1, b2, &b3);
printf("b1=%d, b2=%d, b3=%d\n", b1, b2, b3);
// imprime b1=10, b2=2, b3=3
}
O efeito o mesmo para b2 e b3, mas repare que no caso de b3 passado o endereo explicitamente, e
a funo tem que tratar o parmetro como tal.
Falta ainda analisar um outro uso de referncias, quando esta aparece como tipo de retorno de uma
funo. Por exemplo:
int& f()
{
static int global;
return global;
// retorna uma referncia para a varivel
}
void main()
{
f() = 12;
}

// altera a varivel global

importante notar que este exemplo vlido porque global uma varivel static de f, ou seja, uma
varivel global com escopo limitado a f. Se global fosse uma varivel local comum, o valor de retorno
seria invlido, pois quando a funo f terminasse a varivel global no existiria mais, e portanto a
referncia seria invlida. Como um ponteiro perdido. Outras sees apresentam exemplos prticos desta
utilizao.

Alocao de memria
A alocao dinmica de memria, que em C era tratada com as funes malloc e free, diferente em
C++. Programas C++ no precisam mais usar estas funes. Para o gerenciamento da memria, foram
criados dois novos operadores, new e delete, que so duas palavras reservadas. O operador new aloca
memria e anlogo ao malloc; delete desaloca memria e anlogo ao free. A motivao desta
modificao ficar clara no estudo de classes, mais especificamente na parte de construtores e destrutores.
Por ser um operador da linguagem, no mais necessrio, como no malloc, calcular o tamanho da
rea a ser alocada. Outra preocupao no mais necessria a converso do ponteiro resultado para o tipo
correto. O operador new faz isso automaticamente:
int * i1 = (int*)malloc(sizeof(int));
int * i2 = new int;

// C
// C++

A alocao de um vetor tambm bem simples:


int * i3 = (int*)malloc(sizeof(int) * 10); // C
int * i4 = new int[10];
// C++
A liberao da memria alocada feita pelo operador delete. Este operador pode ser usado em duas
formas; uma para desalocar um objeto e outra para desalocar um vetor de objetos:
free(i1);
delete i2;
free(i3);
delete [] i4;

//
//
//
//

alocado
alocado
alocado
alocado

com
com
com
com

malloc
new
malloc
new[]

(C )
(C++)
(C )
(C++)

A utilizao de delete para desalocar um vetor, assim como a utilizao de delete[] para desalocar um
nico objeto tem consequncias indefinidas. A necessidade de diferenciao se explica pela existncia de
destrutores, apresentados mais frente.

Valores default para parmetros de funes


Em C++ existe a possibilidade de definir valores default para parmetros de uma funo. Por
exemplo, uma funo que imprime uma string na tela em alguma posio especificada. Se nenhuma
posio for especificada, a string deve ser impressa na posio do cursor:
void impr( char* str, int x = -1, int y = -1)
{
if (x == -1) x = wherex();
if (y == -1) y = wherey();
gotoxy( x, y );
cputs( str );
}
Esta definio indica que a funo impr tem trs parmetros, sendo que os dois ltimos tem valores
default. Quando uma funo usa valores default, os parmetros com default devem ser os ltimos. A
funo acima pode ser utilizada das seguintes maneiras:
impr( "especificando a posio", 10, 10 ); // x=10, y=10
impr( "s x", 20 );
// x=20, y=-1
impr( "nem x nem y" );
// x=-1, y=-1
O uso deste recurso tambm envolve alguns detalhes. A declarao do valor default s pode aparecer
uma vez. Isto deve ser levado em considerao ao definir funes com prottipos. A maneira correta
especifica o valor default apenas no prottipo:
void impr( char* str, int x = -1, int y = -1 );
...
void impr( char* str, int x, int y )
{
...
}
Mas a regra diz apenas que os valores s aparecem em um lugar. Nada impede a seguinte construo:
void impr( char* str, int x, int y );
...
void impr( char* str, int x = -1, int y = -1 )
{
...
}
Nesse caso, antes da definio do corpo de impr ela ser tratada como uma funo sem valores
default.

Sobrecarga de nomes de funes


Este novo recurso permite que um nome de funo possa ter mais de um significado, dependendo do
contexto. Ter mais de um significado quer dizer que um nome de funo pode estar associado a vrias
implementaes diferentes. Apesar disso soar estranho para pessoas familiarizadas com C, no dia a dia
aparecem situaes idnticas no uso da nossa linguagem. Consideremos o verbo tocar; podemos us-lo
em diversas situaes. Podemos tocar violo, tocar um disco etc. Em C++, diz-se que o verbo tocar est
sobrecarregado. Cada contexto est associado a um significado diferente, mas todos esto
conceitualmente relacionados.
Um exemplo simples uma funo que imprime um argumento na tela. Seria interessante se
pudssemos usar esta funo para vrios tipos de argumentos, mas cada tipo exige uma implementao
diferente. Com sobrecarga isto possvel; o compilador escolhe que implementao usar dependendo do
contexto. No caso de funes o contexto o tipo dos parmetros (s os parmetros, no os resultados):
display( "string" );
display( 123 );
display( 3.14159 );
Na realidade, a maioria das linguagens usa sobrecarga internamente, mas no deixa este recurso
disponvel para o programador. C uma destas linguagens. Por exemplo:

a = a + 125;
b = 3.14159 + 1.17;
O operador + est sobrecarregado. Na primeira linha, ele se refere a uma soma de inteiros; na segunda
a uma soma de reais. Estas somas exigem implementaes diferentes, e como se existissem duas
funes, a primeira sendo
int +(int, int);
e a outra como
float +(float, float);
Voltando funo display, preciso definir as vrias implementaes requeridas:
void display( char *v ) { printf("%s", v); }
void display( int
v ) { printf("%d", v); }
void display( float v ) { printf("%f", v); }
A simples declarao destas funes j tem todas as informaes suficientes para o compilador fazer a
escolha correta. Isto significa que as funes no so mais distinguidas apenas pelo seu nome, mas pelo
nome e pelo tipo dos parmetros.
A tentativa de declarar funes com mesmo nome que no possam ser diferenciadas pelo tipo dos
parmetros causa um erro:
void f(int a);
int f(int b); // erro! redeclarao de f!!!
O uso misturado de sobrecarga e valores default para parmetros tambm pode causar erros:
void f();
void f(int a = 0);
void main()
{
f( 12 ); // ok, chamando f(int)
f();
// erro!! chamada ambgua: f() ou f(int = 0)???
}
Repare que nesse caso o erro ocorre no uso da funo, e no na declarao.

Parmetros de funes no utilizados


A maioria dos compiladores gera uma warning quando um dos parmetros de uma funo no
utilizado no seu corpo. Por exemplo:
void f(int a)
{
return 1;
}
Este um caso tpico. O aviso do compilador faz sentido, j que a no utilizao do parmetro a pode
ser uma erro na lgica da funo. Mas e se for exatamente esta a implementao da funo f? primeira
vista pode parecer estranho uma funo que no utilize algum de seus parmetros, mas em programao
com callbacks (isto ficar mais claro a partir da introduo de programao orientada a eventos) comum
as funes ignorarem alguns parmetros. Se for este o caso, no necessrio conviver com as warnings
do compilador. Basta omitir o nome do parmetro que no ser usado:
void f(int)
{
return 1;
}
Esta uma funo que recebe um inteiro como parmetro, mas este inteiro no utilizado na
implementao da funo.

Operador de escopo
C++ possui um novo operador que permite o acesso a nomes declarados em escopos que no sejam o
corrente. Por exemplo, considerando o seguinte programa C:
char *a;
void main(void)
{
int a;
a = 23;
/* como acessar a varivel global a??? */
}
A declarao da varivel local a esconde a global. Apesar de a varivel a global existir, no h como
referenciar o seu nome, pois no escopo da funo main, o nome a est associado local a.
O operador de escopo possibilita o uso de nomes que no esto no escopo corrente, o que pode ser
usado neste caso:
char *a;
void main(void)
{
int a;
a = 23;
::a = "abc";
}
A sintaxe deste operador a seguinte:
escopo::nome,
onde escopo o nome da classe onde est declarado no nome nome. Se escopo no for especificado,
como no exemplo acima, o nome procurado no espao global.
Outros usos deste operador so apresentados nas sees sobre classes aninhadas, campos de estruturas
static e mtodos static.

Incompatibilidades entre C e C++


Teoricamente, um programa escrito em C compatvel com um compilador C++. Na realidade esta
compatibilidade no de 100%, pois s o fato de C++ ter mais palavras reservadas j inviabiliza esta
compatibilidade total. Outros detalhes contribuem para que um programa C nem sempre seja tambm um
programa C++ vlido.
Sero discutidos os pontos principais destas incompatibilidades; tanto as diferenas na linguagem
como a utilizao de cdigos C e C++ juntos em um s projeto. No entanto a discusso no ser
extensiva, pois algumas construes suspeitas podem levar a vrias pequenas incompatibilidades, como
por exemplo:
a = b //* comentrio
etc.
*/ 2;
Extrados os comentrios, este cdigo em C fica sendo:
a = b / 2;
Enquanto que em C++, o resultado diferente:
a = b
etc. */ 2;
Casos como estes so incomuns e no vale a pena analis-los.

Palavras reservadas
Talvez a maior incompatibilidade seja causada pelo simples fato que C++ tem vrias outras palavras
reservadas. Isto significa que qualquer programa C que declare um identificador usando uma destas
palavras no um programa C++ correto. Aqui est uma lista com as novas palavras reservadas:
catch
class
delete
friend
inline

new
operator
private
protected
public

template
this
throw
try
virtual

A nica soluo para traduzir programas que fazem uso destas palavras trocar os nomes dos
identificadores, o que pode ser feito usando diretivas #define.

Exigncia de prottipos
Um dos problemas que pode aparecer durante a compilao de um programa C est relacionado ao
fato de que prottipos no so obrigatrios em C. Como C++ exige prottipos, o que era uma warning C
pode se transformar em um erro C++.
Estes erros simplesmente foram o programador a fazer uma coisa que j devia ter sido feita mesmo
com o compilador C: usar prottipos para as funes.
void main(void)
{
printf("teste"); // C:
warning! printf undeclared
// C++: error!
printf undeclared
}

Funes que no recebem parmetros


Programas que utilizam prottipos incompletos, ou seja, prottipos que s declaram o tipo de retorno
de uma funo, podem causar erros em C++. Este tipo de prottipo considerado obsoleto em C, mas
ainda vlido. Em C++, estes prottipos so interpretados como declaraes de funes que no recebem
parmetros, o que certamente causar erros se a funo receber algum:
float square();

// C:
//
// C++:
//

prottipo incompleto, no se sabe nada


sobre os parmetros
prottipo completo,
no recebe parmetros

void main(void)
{
float a = square(3); // C:
ok
// C++: error! too many arguments
}

Estruturas aninhadas
Outra incompatibilidade pode aparecer em programas C que declaram estruturas aninhadas. Em C, s
h um escopo para os tipos: o global. Por esse motivo, mesmo que uma estrutura tenha sido declarada
dentro de outra, ou seja, aninhada, ela pode ser usada como se fosse global. Por exemplo:
struct S1 {
struct S2 {
int a;
} b;
};
void main(void)
{
struct S1 var1;

Esta lista no definitiva, j que constantemente novos recursos vo sendo adicionados ao padro.

struct S2 var2;

// ok em C, errado em C++

}
Este um programa correto em C. Em C++, uma estrutura s vlida no escopo em que foi declarada.
No exemplo acima, por ter sido declarada dentro de S1, a estrutura S2 s pode ser usada dentro de S1.
Nesse caso, a tentativa de declarar a varivel var2 causaria um erro, pois S2 no um nome global.
Em C++, S2 pode ser referenciada utilizando-se o operador de escopo:
struct S1::S2 var2; // soluo em C++

Uso de bibliotecas C em programas C++


Esta seo discute um problema comum ao se tentar utilizar bibliotecas C em programas C++. A
origem do problema o fato de C++ permitir sobrecarga de nomes de funes. Vamos analisar o trecho
de cdigo abaixo:
void f(int);
void main(void)
{
f(12);
}
O programa acima declara uma funo f que ser chamada com o argumento 12. O cdigo final
gerado para a chamada da funo ter uma linha assim:
call nome-da-funo-escolhida
Este nome deve bater com o nome gerado durante a compilao da funo f, seja qual for o mdulo a
que ela pertence. Caso isto no acontea, a linkedio do programa sinalizar erros de smbolos
indefinidos. Isto significa que existem regras para a gerao dos nomes dos identificadores no cdigo
gerado. Em C, esta regra simples: os smbolos tm o mesmo nome do cdigo fonte com o prefixo _. No
caso acima, o cdigo gerado seria:
call _f
Entretanto, C++ no pode usar esta mesma regra simples, porque um mesmo nome pode se referir a
vrias funes diferentes pelo mecanismo de sobrecarga. Basta imaginar que poderamos ter:
void f(int);
void f(char*);
Nesse caso a regra de C no suficiente para diferenciar as duas funes. O resultado que C++ tem
uma regra complexa para gerao dos nomes, que envolve codificao dos tipos tambm. O compilador
Borland C++ 4.0 codifica as funes acima, respectivamente, como:
@f$qi
@f$qpc
Isto deve ser levado em considerao na compilao da chamada da funo. A chamada de f com o
argumento 12 pode gerar dois cdigos diferentes. Se a funo f for na realidade uma funo compilada em
C, o seu nome ser _f, enquanto que se esta for uma funo C++, o seu nome ser @f$qi. Qual deve ser
ento o cdigo gerado? Qual nome deve ser chamado?
A nica maneira de resolver este problema indicar, no prottipo da funo, se esta uma funo C
ou C++. No exemplo acima, o compilador assume que a funo C++. Se f for uma funo C, o seu
prottipo deve ser:
extern "C" void f(int);
A construo extern C pode ser usada de outra maneira, para se aplicar a vrias funes de uma vez:
extern "C"{
void f(int);
void f(char);
// ...
}

Dentro de um bloco extern pode aparecer qualquer tipo de declarao, no apenas funes. A soluo
ento alterar os header files das bibliotecas C, explicitando que as funes no so C++. Para isso, basta
envolver todas as declaraes em um bloco destes.
Como uma declarao extern C s faz sentido em C++, ela causa um erro de sintaxe em C. Para que
os mesmos header files sejam usados em programas C e C++, basta usar a macro pr-definida
__cplusplus, definida em todo compilador C++. Com estas alteraes, um header file de uma biblioteca C
ter a seguinte estrutura:
#ifdef __cplusplus
extern "C" {
#endif
// todas as declaraes
// ...
#ifdef __cplusplus
}
#endif

Exerccio 2 - Calculadora RPN com novos recursos de C++


Analisar a calculadora alterando-a de acordo com os novos recursos de C++

Captulo 2b
Recursos de C++ relacionados s classes
Classes aninhadas
Declaraes de classes podem ser aninhadas. Uma classe aninhada no reside no espao de nomes
global, como era em C. Para referenciar uma classe aninhada fora do seu escopo, preciso usar o
operador de escopo.
Este aninhamento usado para encapsular classes auxiliares que s fazem sentido dentro de um
escopo reduzido.
struct A {
struct B {
int b;
};
int a;
};
void main()
{
A a;
A::B b;
}
O cdigo acima simplesmente declara a estrutura B dentro de A; no existe nenhum campo de A que
seja do tipo B.

Declarao incompleta
Em C, a declarao de estruturas mutuamente dependentes feita naturalmente:
struct A { struct B *next; };
struct B { struct A *next; };
Em C++ a construo acima pode ser usada. No entanto, como a declarao de uma estrutura em C++
j define o nome como um tipo sem necessidade de typedefs, normal em C++ o uso de tipos sem o uso
da palavra reservada struct:
struct A { B *next; }; // Erro! B indefinido
struct B { A *next; };
No caso de tipos mutuamente dependentes, isto s possvel usando uma declarao incompleta:
struct B; // declarao incompleta de B
struct A { B *next; };
struct B { A *next; };
Assim como em C, antes da declarao completa da estrutura s possvel usar o nome para declarar
ponteiros e referncias deste tipo. Como as informaes esto incompletas, no possvel declarar
variveis deste tipo nem utilizar campos da estrutura.

Mtodos const
Mtodos const so mtodos que no alteram o estado interno de um objeto. Assim como era possvel
declarar variveis e parmetros const em C, possvel declarar um mtodo const em C++. A
especificao const faz parte da declarao do mtodo.
A motivao para esta nova declarao pode ser esclarecida com o exemplo abaixo:
struct A {
int value;
int get()
{ return value; }
void put (int v) { value = v; }
};

void f(const A a)
{
// parmetro a no pode ser modificado
// quais mtodos de a podem ser chamados???
int b = a.get(); // Erro! mtodo no const
}
A funo f no pode alterar o seu parmetro por causa da declarao const. Isto significa que os
campos de a podem ser lidos mas no podem ser modificados. E quanto aos mtodos? O que define quais
mtodos podem ser chamados a sua implementao. Esta verificao no pode ser feita pelo
compilador, j que na maioria das vezes o cdigo dos mtodos no est disponvel. responsabilidade do
programador declarar quais mtodos no modificam o estado interno do objeto. Estes mtodos podem ser
aplicados a objetos declarados const.
No exemplo acima, o mtodo get pode ser declarado const, o que possibilita a sua chamada na funo
f. Se o mtodo put for declarado const, o compilador acusa um erro de compilao pois o campo value
modificado em sua implementao:
struct A {
int value;
int get() const { return value; }
void put (int v) { value = v; }
};
void f(const A a)
{
// parmetro a no pode ser modificado
int b = a.get(); // ok, mtodo const
}

this
Em todo mtodo no static (ver seo sobre mtodos static), a palavra reservada this um ponteiro
para o objeto sobre o qual o mtodo est executando.
Todos os mtodos de uma classe so sempre chamados associados a um objeto. Durante a execuo
de um mtodo, os campos do objeto so manipulados normalmente, sem necessidade de referncia ao
objeto. E se um mtodo precisar acessar no os campos de um objeto, mas o prprio objeto? O exemplo
abaixo ilustra este caso:
struct A {
int i;
A& inc();
};
// Este mtodo incrementa o valor interno
// e retorna o prprio objeto
A& A::inc()
{
// estamos executando este cdigo para obj1 ou obj2?
// como retornar o prprio objeto?
i++;
return *this; // this aponta para o prprio objeto
}
void main()
{
A obj1, obj2;
obj1.i = 0;
obj2.i = 100;
for (j=0; j<10; j++)

printf("%d\n", (obj1.inc()).i);
}
O tipo de this dentro de um mtodo de uma classe X
X* const
a no ser que o mtodo seja declarado const. Nesse caso, o tipo de this :
const X* const

Campos de estrutura static


Em C++, membros de uma classe podem ser static. Quando uma varivel declarada static dentro de
uma classe, todas as instncias de objetos desta classe compartilham a mesma varivel. Uma varivel
static uma varivel global, s que com escopo limitado classe.
A declarao de um campo static aparece na declarao da classe, junto com todos os outros campos.
Como a declarao de uma classe normalmente includa em vrios mdulos de uma mesma aplicao
via header files (arquivos .h), a declarao dentro da classe equivalente a uma declarao de uma
varivel global extern. Ou seja, a declarao apenas diz que a varivel existe; algum mdulo precisa
defini-la. O exemplo abaixo ilustra a utilizao de campos static:
struct A {
int a;
static int b; // declara a varivel,
// equivalente ao uso de extern para
// variveis globais
};
int A::b = 0;

// define a varivel criando o seu espao


// esta a hora de inicializar

void main(void)
{
A a1, a2;
a1.a = 0; // modifica o campo a de a1
a1.b = 1; // modifica o campo b compartilhado por a1 e a2
a2.a = 2; // modifica o campo a de a1
a2.b = 3; // modifica o campo b compartilhado por a1 e a2
printf("%d %d %d %d", a1.a, a1.b, a2.a, a2.b);
// imprime 0 3 2 3
}
Se a definio
int A::b = 0;
for omitida, o arquivo compilado mas o linker acusa um erro de smbolo no definido.
Como uma varivel esttica nica para todos os objetos da classe, no necessrio um objeto para
referenciar este campo. Isto pode ser feito com o operador de escopo:
A::b = 4;

Mtodos static
Assim como campos static so como variveis globais com escopo reduzido classe, mtodos static
so como funes globais com escopo reduzido classe. Isto significa que mtodos static no tem o
parmetro implcito que indica o objeto sobre o qual o mtodo est sendo executado (this), e portanto
apenas os campos static podem ser acessados:
struct A {
int a;
static int b;
static void f();
};

int A::b = 10;


void A::f()
{
a = 10; // errado, a s faz sentido com um objeto
b = 10; // ok, b declarado static
}

Ponteiros para mtodos


possvel obter o ponteiro para um mtodo. Isto pode ser feito aplicando o operador & a um nome de
mtodo totalmente qualificado, ou seja:
& classe::mtodo
Uma varivel do tipo ponteiro para um membro da classe X obtida com o declarador X::*:
struct A {
void f(int);
};
void main()
{
void (A::*p) (int); // declara a varivel p
p = &A::f;
// obtm o ponteiro para f
A a1;
A* a2 = new A;
(a1.*p)(10);
(a2->*p)(20);

// aplica p a a1
// aplica p a a2

}
A aplicao dos mtodos feita utilizando-se os novos operadores .* e ->*.

Captulo 3
Encapsulamento
Como foi visto anteriormente, um TAD definido pela sua interface, ou seja, como ele manipulado.
Um TAD pode ter diversas implementaes possveis, e, independentemente desta ou daquela
implementao, objetos deste tipo so usados sempre da mesma forma. Os atributos alm da interface, ou
seja, os atributos dependentes da implementao, no precisam e no devem estar disponveis para o
usurio de um objeto, pois este deve ser acessado exclusivamente atravs da interface definida. O ato de
esconder estas informaes chamado de encapsulamento.
Os mecanismos apresentados at aqui permitem a definio da interface em um tipo, permitindo
cdigos bem mais modulares e organizados. No entanto, no permitem o encapsulamento de dados e/ou
cdigo.

Controle de acesso - public e private


Parte do objetivo de uma classe esconder o mximo de informao possvel. Ento necessrio
impor certas restries na maneira como uma classe pode ser manipulada, e que dados e cdigo podem
ser usados. Podem ser estabelecidos trs nveis de permisso de acordo com o contexto de uso dos
atributos:
nos mtodos da classe
a partir de objetos da classe
mtodos de classes derivadas (este ponto ser visto posteriormente)
Cada um destes trs contextos tm privilgios de acesso diferenciados; cada um tem uma palavra
reservada associada, private, public e protected respectivamente.
O exemplo abaixo ilustra o uso destas novas palavras reservadas:
struct controle {
private:
int a;
int f1( char* b );
protected:
int b;
int f2( float );
public:
int c;
float d;
void f3( controle* );
};
As sees podem ser declaradas em qualquer ordem, inclusive podem aparecer mais de uma vez. O
exemplo abaixo equivalente ao apresentado acima:
struct controle {
int c;
int d;
private:
int a;
protected:
int b;
protected:
int f2( float );
public:
void f3( controle* );
private:
int f1( char* b );
};
Atributos private so os mais restritos. Somente a prpria classe pode acessar os atributos privados.
Ou seja, somente os mtodos da prpria classe tem acesso a estes atributos.
struct SemUso {
private:

int valor;
void f1() { valor = 0; }
int f2() { return valor; }
};
int main()
{
SemUso p; // cria um objeto do tipo SemUso
p.f1();
// erro, f1 private!
printf("%d", p.f2() );
// erro, f2 private
return 0;
}
No exemplo anterior, todos os membros so privados, o que impossibilita o uso de um objeto da classe
SemUso. Para que as funes f1 e f2 pudessem ser usadas, elas precisariam ser pblicas. O cdigo a
seguir uma modificao da declarao da classe SemUso para tornar as funes f1 e f2 pblicas e
portanto passveis de serem chamadas em main.
struct SemUso {
private:
int valor;
public:
void f1() { valor = 0; }
int f2() { return valor; }
};
Membros protected sero explicados quando forem introduzidas as classes derivadas.
Para exemplificar melhor o uso do controle de acesso, vamos considerar uma implementao de um
conjunto de inteiros. As operaes necessrias so, por exemplo, inserir e retirar um elemento, verificar se
um elemento pertence ao conjunto e a cardinalidade do conjunto. Ento o nosso conjunto ter pelo menos
o seguinte:
struct Conjunto {
void insere(int n);
void retira(int n);
int pertence(int n);
int cardinalidade();
};
Se a implementao deste conjunto usar listas encadeadas, preciso usar uma estrutura auxiliar
elemento que seriam os ns da lista. A nova classe teria ainda uma varivel private que seria o ponteiro
para o primeiro elemento da lista. Outra varivel private seria um contador de elementos. Vamos
acrescentar ainda um mtodo para limpar o conjunto, para ser usado antes das funes do conjunto
propriamente ditas. Eis a definio da classe:
struct Conjunto {
private:
struct listElem {
listElem *prox;
int valor;
};
listElem* lista;
int nElems;
public:
void limpa() { nElems=0; lista=NULL; }
void insere(int n);
void retira(int n);
int pertence(int n);
int cardinalidade();
};
Como somente os mtodos desta classe tem acesso aos campos privados, a estrutura interna no pode
ser alterada por quem usa o conjunto, o que garante a consistncia do conjunto.

Declarao de classes com a palavra reservada class


As classes podem ser declaradas usando-se a palavra reservada class no lugar de struct. A diferena
entre as duas opes o nvel de proteo caso nenhum dos especificadores de acesso seja usado. Os
membros de uma struct so pblicos, enquanto que em uma class, os membros so privados:
struct A {
int a; // a pblico
};
class B {
int a;
};

// a privado

Como as interfaces das classes devem ser as menores possveis e devem ser tambm explcitas, as
declaraes com a palavra class so mais usadas. Com class, somente os nomes explicitamente declarados
como public so exportados.
A partir de agora os exemplos vo ser feitos usando-se a palavra class.

Classes e funes friend


Algumas vezes duas classes so to prximas conceitualmente que seria desejvel que uma delas
tivesse acesso irrestrito aos membros da outra.
No exemplo anterior, no h meio de percorrer o conjunto para, por exemplo, imprimir todos os
elementos. Para fazer isto, seria preciso ter acesso ao dado lista e estrutura listElem, ambos private.
Uma maneira de resolver este problema seria aumentar a interface do conjunto oferecendo funes para
percorrer os elementos. Esta soluo seria artificial, pois estas operaes no fazem parte do TAD
conjunto.
A soluo normalmente usada a idia de iteradores. Um iterador atua sobre alguma coleo de
elementos e a cada chamada retorna um elemento diferente da coleo, at que j tenha retornado todos.
Um iterador para o nosso conjunto seria:
class IteradorConj {
Conjunto::listElem *corrente;
public:
void inicia( Conjunto* c ) { corrente = c->lista; }
int terminou()
{ return corrent==NULL; }
int proxElem()
{ int n=corrente->valor; corrent=corrente->prox; return n; }
};
void main()
{
Conjunto conj; // cria conjunto
conj.limpa(); // inicializa para operaes
conj.insere(10); //insere o elemento 10
// ...
IteradorConj it; // cria um iterador
it.inicia( &conj ); // inicializa o iterador com conj
while (!it.terminou())
// percorre todos os elementos
printf("%d\n", it.proxElem() ); // imprimindo-os
}
O problema aqui que o iterador usa dados privados de Conjunto, o que gera erros durante a
compilao. Esse um caso em que as duas classes esto intimamente ligadas, e ento seria conveniente,
na declarao de Conjunto, dar acesso irrestrito classe IteradorConj.
Para isso existe a palavra reservada friend. Ela serve para oferecer acesso especial algumas classes
ou funes. Na declarao da classe Conjunto possvel dar permisso irrestrita aos seus atributos. A
classe Conjunto chega sua forma final:
class Conjunto {
friend class IteradorConj;
struct

listElem {

...
...
Tambm possvel declarar funes friend. Nesse caso, a funo ter acesso irrestrito aos
componentes da classe que a declarou como friend. Exemplo de funo friend:
class No {
friend int leValor( No* ); // d acesso privilegiado
// funo leValor
int valor;
public:
void setaValor( int v ) { valor=v; }
};
int leValor( No* n )
{
return n->valor; // acessa dado private de No
}
O recurso de classes e funes friend devem ser usados com cuidado, pois isto um furo no
encapsulamento dos dados de uma classe. Projetos bem elaborados raramente precisam lanar mo de
classes ou funes friend.

Exerccio 3 - Calculadora RPN com controle de acesso


Modificar a calculadora de acordo com o controle de acesso

Construtores e destrutores
Como o nome j indica, um construtor uma funo usada para construir um objeto de uma dada
classe. Ele chamado automaticamente assim que um objeto criado. Analogamente, os destrutores so
chamados assim que os objetos so destrudos.
Assim como o controle de acesso desempenha um papel importante para manter a consistncia interna
dos objetos, os construtores so fundamentais para garantir que um objeto recm criado esteja tambm
consistente.
No exemplo onde foi implementada a classe Conjunto, foi necessria a introduo de uma funo,
limpa, que precisava ser chamada para inicializar o estado do objeto. Se esta funo no for chamada
antes da manipulao de cada objeto, os resultados so imprevisveis (os campos nElems e lista contm
lixo). Deixar esta chamada a cargo do programador aumentar o potencial de erro do programa. Na
realidade, a funo limpa deveria ser um construtor de Conjunto. O compilador garante que o construtor
a primeira funo a ser executada sobre um objeto. A mesma coisa acontecia em IteradorConj. A funo
inicia deveria ser um construtor, pois antes desta chamada o estado do objeto imprevisvel.
Destrutores so normalmente utilizados para liberar recursos alocados pelo objeto, como memria,
arquivos etc.

Declarao de construtores e destrutores


Os construtores e destrutores so mtodos especiais. Nenhum dos dois tem um tipo de retorno, e o
destrutor no pode receber parmetros, ao contrrio do construtor.
A declarao de um construtor feita definindo-se um mtodo com o mesmo nome da classe. O nome
do destrutor o nome da classe precedido de ~ (til). Ou seja, um construtor de uma classe X declarado
como um mtodo de nome X, o nome do destrutor ~X; ambos sem tipo de retorno. O destrutor no pode
ter parmetros; o construtor pode. Pelo mecanismo de sobrecarga (funes so diferenciadas no apenas
pelo nome, mas pelos parmetros tambm), classes podem ter mais de um construtor.
Com o uso de construtores, as classe Conjunto e IteradorConj passa a ser:
class Conjunto {
friend class IteradorConj;
struct listElem {
listElem *prox;
int valor;
};
listElem* lista;
int nElems;

public:
Conjunto() { nElems=0; lista=NULL; }
~Conjunto(); // desaloca os ns da lista
void insere(int n);
void retira(int n);
int pertence(int n);
int cardinalidade();
};
class IteradorConj {
Conjunto::listElem *corrente;
public:
IteradorConj( Conjunto* c ) { corrente = c->lista; }
int terminou()
{ return corrent==NULL; }
int proxElem()
{ int n=corrente->valor; corrent=corrente->prox; return n; }
};

Chamada de construtores e destrutores


Os construtores so chamados automaticamente sempre que um objeto da classe for criado. Ou seja,
quando a varivel declarada (objetos alocados na pilha) ou quando o objeto alocado com new (objetos
dinmicos alocados no heap).
Os destrutores de objetos alocados na pilha so chamados quando o objeto sai do seu escopo. O
destrutor de objetos alocados com new s chamado quando estes so desalocados com delete:
struct A {
A() { printf("construtor\n"); }
~A() { printf("destrutor\n"); }
};
void main()
{
A a1;
// chama construtor
{
A a2;
// chama construtor
A *a3 = new A; // chama construtor
}
// chama destrutor
delete a3;
// chama destrutor
}
// chama destrutor

de a1
de
de
de
de
de

a2
a3
a2
a3
a1

interessante observar que os objetos globais j esto inicializados quando a funo main comea a
ser executada. Isto significa que os construtores de objetos globais so chamados antes de main:
A global;
void main()
{
printf("main\n");
}
Este cdigo produz a seguinte sada:
construtor
main
destrutor

Construtores com parmetros


No exemplo sobre conjuntos, a classe IteradorConj possui um construtor que recebe um parmetro. Se
os construtores existem para garantir a consistncia inicial dos objetos, o cdigo est indicando que, para
que um iterador esteja consistente, necessrio fornecer um conjunto sobre o qual ser feita a iterao.

Se for possvel criar um IteradorConj sem fornecer este conjunto, ento o construtor no est
garantindo nada. Mas no isto que acontece. A declarao de um construtor impe que os objetos s
sejam criados atravs deles. Sendo assim, no mais possvel criar um IteradorConj sem fornecer um
Conjunto. O exemplo abaixo utiliza estas classes e mostra como os construtores so chamados:
void main()
{
Conjunto conj;
conj.insere(10);
// ...
IteradorConj i; // erro! obrigatrio fornecer o parmetro
IteradorConj it( &conj ); // cria um iterador passando conj
while (!it.terminou())
// percorre os elementos
printf("%d ", it.proxElem() ); // imprimindo-os
}
Caso o iterador seja alocado dinamicamente (com new), a sintaxe a seguinte:
IteradorConj *i = new IteradorConj(&conj)
No possvel alocar um vetor de objetos passando parmetros para o construtor. Por exemplo, no
possvel criar um vetor de iteradores:
Conjunto c;
IteradorConj it(&c)[10]; // erro!!!
IteradorConj *pit;
pit = new(&c)[20];
// erro!!!

Construtores gerados automaticamente


O fato de algumas classes no declararem construtores no significa que elas no tenham construtores.
Na realidade, o compilador gera alguns construtores automaticamente.
Um dos construtores s gerado se a classe no declarar nenhum. Este o construtor vazio, que
permite que os objetos sejam criados. Por exemplo, como a classe
class X {
int a;
};
no declara nenhum construtor, o compilador automaticamente gera um construtor vazio pblico para
esta classe. A declarao abaixo equivalente:
class X {
int a;
public: X() {} // construtor vazio
};
Outro construtor gerado o construtor de cpia. Este gerado mesmo que a classe declare algum
outro construtor. O construtor de cpia de uma classe recebe como parmetro uma referncia para um
objeto da prpria classe. A classe X acima possui este construtor:
class X {
int a;
// public: X() {} construtor vazio
// public: X(const X&); construtor de cpia
};
O construtor de cpia constri um objeto a partir de outro do mesmo tipo. O novo objeto uma cpia
byte a byte do objeto passado como parmetro. Repare que a existncia deste construtor no um furo na
consistncia dos objetos, j que ele s pode ser usado a partir de um objeto existente, e portanto,
consistente. Este construtor pode ser chamado de duas formas:
void main()
{
X a1;
X a2(a1);

// usa construtor vazio


// usa construtor de cpia

X a3 = a2; // usa construtor de cpia


}
A atribuio s chama o construtor de cpia quando usada junto com a declarao do objeto.
importante notar que este construtor pode ser redefinido, basta declarar um construtor com a mesma
assinatura (ou seja, recebendo como parmetro uma referncia para um objeto da prpria classe).

Objetos temporrios
Assim como no preciso declarar uma varivel dos tipos primitivos sempre que se quer usar um
valor temporariamente, possvel criar objetos temporrios em C++. Uma utilizao tpica a seguinte:
quando uma funo aceita como parmetro um inteiro, e voc quer cham-la passando o valor 10, no
necessrio atribuir 10 a uma varivel apenas para chamar a funo. O mesmo deve se aplicar a tipos
definidos pelo usurio (classes):
class A {
public:
A(int);
~A();
};
void f(A);
void main()
{
A a1(1);
f(a1);
f(A(10)); // cria um objeto temporrio do tipo A
// passando 10 para o construtor
// aps a chamada a f o objeto destrudo
}

Converso por construtores


Um construtor com apenas um parmetro pode ser encarado como uma funo de converso do tipo
do parmetro para o tipo da classe. Por exemplo,
class A {
public:
A(int);
A(char*, int = 0);
};
void f(A);
void main()
{
A a1 = 1;
A a2 = "abc";
a1 = 2;
f(3);
}

//
//
//
//

a1 = A(1)
a2 = A("abc", 0)
a1 = A(2)
f(A(3))

Esta converso s feita em um nvel. Ou seja, se o construtor da classe A no aceita um determinado


tipo, no feita uma tentativa de converter via outros construtores o tipo dado para o tipo aceito pelo
construtor:
class A {
public: A(int);
};
class B {
public: B(A);
};

B a = 1; // erro: B(A(1)) no tentado


No exemplo acima, pelo menos uma converso deve ser feita explicitamente:
B a = A(1)

Construtores privados
Os construtores, assim como qualquer mtodo, podem ser privados. Como o construtor chamado na
criao, os objetos s podero ser criados com este construtor dentro de mtodos da prpria classe ou em
classes e funes friend.

Destrutores privados
Destrutores tambm podem ser privados. Isto significa que objetos desta classe s podem ser
destrudos onde os destrutores podem ser chamados (mtodos da prpria classe, classes e funes friend).
Usando este recurso, possvel projetar classes cujos objetos no so nunca destrudos. Outra
possibilidade o projeto de objetos que no podem ser alocados na pilha, apenas dinamicamente.
Exemplo:
class A {
~A() {}
public:
int a;
};
void main()
{
A a1;

//
//
//
A* a2 = new A; //

erro! destrutor privado,


no pode ser chamado
quando o objeto sair do escopo
ok, s no pode usar delete depois

}
No exemplo acima, o destrutor privado impe duas restries: objetos no podem ser alocados na
pilha e, mesmo que sejam criados dinamicamente, no podem nunca ser destrudos. Para permitir a
destruio dos objetos basta criar um mtodo que faa isso:
class A {
~A() {}
public:
int a;
void destroy() { delete this; }
};

Inicializao de campos de classes com construtores


Quando um objeto no tem um construtor sem parmetros, preciso passar obrigatoriamente valores
como os parmetros. No caso do objeto ser uma varivel, basta definir os parmetros na hora da
declarao:
class A {
public: A(int);
};
A a(123);
Mas e se o objeto for um campo de uma outra classe? Nesse caso ele estar sendo criado quando um
objeto desta outra classe for criado, e nessa hora os parmetros precisaro estar disponveis:
class A {
public: A(int);
};

class B {
A a;
};
Ao se criar um objeto do tipo B, que inteiro deve ser passado ao campo a? Nesse caso, o construtor de
B tem que especificar este inteiro, e o compilador no gera um construtor vazio. Ou seja, a classe B tem
que declarar um construtor para que seja possvel criar objetos deste tipo. A sintaxe a seguinte:
class B {
A a;
public:
B() : a(3) {}
};

Exerccio 4 - Calculadora RPN com construtores


Utilizar construtores na calculadora

Captulo 4
Sobrecarga de operadores
O uso de funes sobrecarregadas no s uniformiza chamadas de funes para diferentes objetos
como tambm permite que os nomes sejam mais intuitivos. Se um dos objetivos da sobrecarga permitir
que as funes sejam chamadas pelo nome mais natural possvel, no importa se o nome j foi usado,
porque no deixar o programador sobrecarregar tambm os operadores?
Na realidade, um operador executa algum cdigo com alguns parmetros, assim como qualquer
funo. A aplicao de um operador equivalente chamada de uma funo. Em C++ permitido
sobrecarregar um operador, com o objetivo de simplificar a notao e uniformizar a expresso.
Existem duas maneiras de implementar operadores para classes de objetos: como funes membro e
como funes globais. Por exemplo, dado um objeto w e um operador unrio !, a expresso
!w
equivalente s chamadas de funes
w.operator!();
operator!(w);

// usando uma funo membro


// usando uma funo global

Vejamos agora como seria com um operador binrio, por exemplo, &. A expresso
x & y
equivalente s chamadas
x.operator&(y); // usando uma funo membro
operator&(x,y); // usando uma funo global
Um detalhe importante que uma funo y.operator&(x) nunca ser considerada pelo compilador
para resolver a expresso x&y, j que isto implicaria que o operador comutativo.
Antes do primeiro exemplo, precisamos ter em mente que C++ no permite a criao de novos
operadores; s podem ser redefinidos os operadores que j existem na linguagem. Isto implica que, por
exemplo, o operador / ser sempre binrio. Outra caracterstica que a prioridade tambm no pode ser
alterada, preservada a original do C.
Um nmero complexo pode ser modelado com uma classe que permita que as operaes matemticas
sobre ele sejam feitas da mesma maneira que os tipos primitivos, ou seja, com os operadores +, - etc. Uma
possibilidade seria:
class Complex {
public:
Complex operator+ (const Complex&);
Complex operator- (const Complex&);
};
Com estes operadores, possvel fazer:
void main()
{
Complex c1, c2, c3;
c1 = c2 + c3;
}

Exerccio 5 - Classe Complex


Implementar uma classe que represente um nmero complexo.

Operadores como funes globais


Suponhamos que a classe que modela complexos possui um construtor da forma:
Complex(float re = 0.0, float im = 0.0);
Este construtor permite criar um complexo especificando suas partes real e imaginria, s
especificando a parte real ou ainda sem dizer nada. Em particular, este construtor define como converter

um float em um complexo, o que equivalente a chamar o construtor com apenas um parmetro. Esta
converso permite expresses do tipo:
c1 = c2 + 3.0; // equivalente a c1 = c2 + Complex(3.0, 0.0)
Considerando que o operador uma funo, esta expresso poderia ser vista como:
c1 = c2.operator+(Complex(3.0,0.0));
A converso foi feita porque a funo operator+ espera um Complex, e o valor era um float. Nesse
caso o compilador converte automaticamente o valor. Mas e se a expresso for a seguinte:
c1 = 3.0 + c2;
Nesse caso 3.0 no parmetro de funo nenhuma, ento a converso no feita. Para possibilitar
esta expresso, seria preciso converter o valor explicitamente:
c1 = Complex(3.0) + c2;
No entanto, se 3.0 fosse o parmetro para alguma funo, o compilador saberia fazer a converso.
Lembrando que os operadores podem ser definidos como mtodos ou como funes globais, possvel
tornar 3.0 um parmetro. o caso de definir o operador como uma funo global:
Complex operator+ (const Complex&, const Complex&);
Com esta funo definida, os dois operandos passam a ser parmetros, e ambos podem ser convertidos
automaticamente.

Operadores que podem ser redefinidos


A maior parte dos operadores podem ser sobrecarregados. So eles:
new delete
+ - * / % ^& | ~
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- , ->* ->
() []
Tanto as formas unrias como as binrias de
+ - * &
podem ser sobrecarregadas, assim como as formas pr-fixadas ou ps fixadas de
++ -Os seguintes operadores no podem ser sobrecarregados:
. .* :: sizeof ?:
j que estes operadores j tem um significado predefinido (exceto ?:) para objetos de qualquer classe.
A funo de atribuio operator=() definida por default para todos os objetos como a atribuio byte
a byte dos campos do objeto.

Exemplo de redefinio do operador [] - classe Vector


As estratgias usadas para redefinies variam muito de acordo com o operador. Esta seo apresenta
um exemplo que traz vrios detalhes que devem ser levados em considerao dependendo do operador. O
exemplo encapsula um vetor como uma classe com o objetivo de checar as indexaes, evitando que
posies aleatrias da memria sejam acessadas. Um vetor normal de C++ permite a indexao com
qualquer inteiro; mesmo se o ndice estiver alm da rea alocada o acesso permitido, com conseqncias
imprevisveis.
O operador [ ] ser redefinido para permitir o uso dos objetos desta classe da mesma maneira que um
vetor C++. Alm do operador, a classe ter um construtor que aloca os elementos do vetor. O
funcionamento ser o seguinte: o construtor, alm de alocar os elementos, guarda o tamanho do vetor. O
operador, que retorna o elemento correspondente do vetor alocado, checa se o ndice vlido (o
construtor guarda o tamanho) antes de fazer o acesso. Alguma coisa da forma:

float Vector::operator[](int i)
{
if (i>=0 && i<size) return elems[i];
else
{
printf("ndice %d invlido\n", i);
return -1.0;
}
}
No entanto isto no suficiente. preciso lembrar que este operador pode ser usado de duas
maneiras:
a = vetor[10];
vetor[20] = b;
Na primeira linha no h problema. O operador uma funo que retorna um valor, que ser atribudo
varivel a. J na segunda linha a atribuio no permitida, pois a funo retorna o valor da posio 20
do vetor; para a atribuio necessrio saber o endereo da posio 20 do vetor. A soluo retornar uma
referncia para um float. Assim o valor de retorno o endereo (necessrio na segunda linha), mas este
usado como um valor (primeira linha). Se simplesmente mudarmos o tipo de retorno da funo para
float&, um erro ser sinalizado durante a sua compilao, pois o valor -1.0, retornado caso o ndice seja
invlido, no tem endereo. Ou seja, necessrio retornar uma varivel, mesmo nesse caso. Outra
particularidade deve ser observada aqui: como que ser retornado ser o endereo da varivel retornada,
esta precisa continuar existindo mesmo depois que afuno termina a sua execuo. O que significa que
no se pode retornar uma varivel local, pois variveis locais deixam de existir assim que a funo
termina. A implementao abaixo utiliza uma varivel static dentro da funo s para este fim:
class Vector {
float *elems;
int
size;
public:
Vector(int s);
float& operator[](int i);
};
Vector::Vector(int s)
{
size=s;
elems = new float[size];
}
float& Vector::operator[](int i)
{
if (i>=0 && i<size) return elems[i];
else
{
static float lixo;
printf("ndice %d invlido\n", i);
return lixo;
}
}

Operadores de converso
possvel definir operadores especiais para converso de tipos. Alm das converses padro, o
programador pode definir como um objeto pode ser convertido para algum outro tipo. Consideremos uma
classe Arquivo que modela um arquivo. Internamente esta classe pode ter um ponteiro para um arquivo
(tipo FILE*) privado. Se fosse necessrio fazer alguma operao j definida na biblioteca que no tenha
sido mapeada na classe, seria preciso ter acesso ao ponteiro para arquivo. Ao invs de tornar pblico este
campo, seria mais elegante definir como um objeto do tipo Arquivo se converte em um FILE*, que o
objetivo. Esta definio se d da seguinte forma:

class Arquivo {
FILE *file;
public:
Arquivo( char* nome ) { file=fopen(nome, "r"); }
~Arquivo() { fclose(file); }
char read() { return file?fgetc(file):EOF; }
int aberto() { return file!=NULL; }
operator FILE*() { return file; }
}
void main()
{
int i;
Arquivo arq("teste.c");
fscanf( (FILE*)arq, "%d", &i );
}

Exerccio 6 - Classe Complex com operadores globais


Alterar a classe Complex para permitir expresses do tipo 1+c, onde c complexo.

Exerccio 7 - Calculadora RPN para complexos


Modificar a calculadora para operar com nmeros complexos.

Exerccio 8 - Classe String


Implementar uma classe String. A classe deve permitir comparaes, concatenaes, atribuies e
acesso a cada caracter, alm de poder ser utilizada em funes como printf. O usurio desta classe no
precisa se preocupar com tamanho de memria alocado. Ou seja, se for necessrio realocar memria para
uma atribuio ou uma concatenao, esta realocao deve ser feita automaticamente dentro da classe. O
acesso aos caracteres deve ser seguro. Por exemplo, no caso de a string ter 10 caracteres e o usurio tentar
escrever no vigsimo, isto no pode alterar uma rea de memria aleatria.

Captulo 5
Aspectos de reutilizao
Por que software no como hardware? Por que todo desenvolvimento comea do nada?
Deviam existir catlogos de mdulos de software, assim como existem catlogos de chips: quando
ns construmos um novo sistema, ns deveramos estar usando os componentes destes catlogos e
combinando-os, em vez de sempre reinventar a roda. Ns escreveramos menos software, a talvez
faramos um desenvolvimento melhor . Ser que alguns problemas dos quais todo mundo reclama
- custos altos, prazos insuficientes, pouca confiabilidade - no desapareceriam? Por que no
assim?
Talvez voc j tenha ouvido esta argumentao antes; talvez voc prprio j tenha pensado nisso. Em
1968, no famoso workshop da OTAN sobre a crise de software, D. McIlroy j estava pregando a
produo de componentes de software em massa. A reutilizao, como um sonho, no nova.
Qualquer pessoa que lide com o desenvolvimento de software se impressiona com seu carter
repetitivo. Diversas vezes, os programadores constroem funes e programas com o mesmo padro:
ordenao, busca de um elemento, comparao, etc. Uma maneira de interessante de avaliar esta situao
responder a seguinte pergunta: Quantas vezes, nos ltimos seis meses, voc escreveu uma rotina de
busca de um elemento x em uma tabela t ?
As dificuldades tcnicas de reutilizao se tornam mais visveis quando se observa a natureza das
repeties no desenvolvimento de sistemas. Esta anlise revela que apesar dos programadores tenderem a
escrever os mesmos tipos de rotinas diversas vezes, estas no so exatamente iguais. Se fosse, a soluo
mais simples teoricamente; na prtica, porm, muitos detalhes mudam de implementao para
implementao (tipos dos elementos, estrutura de dados associada, etc.).
Apesar das dificuldades, algumas solues foram propostas com relativos sucessos:
Reutilizao de cdigo-fonte: Muito comum no meio cientfico. Muito da cultura UNIX foi difundida
pelos laboratrios e universidades do mundo graas disponibilidade de cdigo-fonte ajudando
estudadentes a estudarem, imitarem e estenderem o sistema. No entanto, esta no a forma mais
utilizada nos meios industrial e comercial alm de que esta tcnica no suporta ocultao de
informao (information hiding).
Reutilizao de pessoal: uma forma muito comum na indstria. Consiste na transferncia de
engenheiros de software de projetos a projetos fazendo a permanncia de know-how na compania e
assegurando a aplicao de experincias passadas em novos projetos. Obviamente, esta uma maneira
no-tcnica e limitada.
Reutilizao de design: A idia por trs desta tcnica que as companhias devem acumular repositrios
de idias descrevendo designs utilizados para os tipos de aplicao mais comuns.

Requisitos para reuso


As idias apresentadas anteriormente, apesar de limitadas, mostram aspectos importantes para a
reutilizao de cdigo:
A noo de reuso de cdigo-fonte lembra que software definido pelo seu cdigo. Uma poltica
satisfatria de reutilizao deve produzir programas (cdigos) reutilizveis.
A reutilizao de pessoal fundamental pois os componentes de software so inteis sem
profissionais bem treinados e com experincia para reconhecer as situaes com possibilidade de reuso.
A reutilizao de design enfatiza a necessidade de componentes reutilizveis que estejam em um nvel
conceitual e de generalidade alto no somente com solues para problemas especficos. Neste
aspecto, poder ser visto como o conceito de classes nas linguagens orientadas por objetos pode ser visto
como mdulos de design e de implementao.
A maneira de produzir mdulos que permitem boa possibilidade de reuso descrita abaixo. Neste
caso, o nosso exemplo de procurar um elemento x em uma tabela t bem ilustrativo.

Variao no tipo
Um mdulo que implemente uma determinada funcionalidade deve ser capaz de faz-lo sobre
qualquer tipo a ele atribudo. Por exemplo, no caso da busca de um elemento x, o mdulo deve ser
aplicvel a diferentes instncias de tipo para x. interessante a utilizao do mesmo mdulo para
procurar um inteiro numa tabela de inteiros ou o registro de um empregado na sua tabela correspondente,
etc.

Variao nas estruturas de dados e algoritmos


No caso da busca de um elemento x, o modo de busca pode ser adaptado para diversos tipos de
estruturas de dados e algoritmos de busca associados: tabelas seqenciais (ordenadas ou no), vetores,
listas, rvores binrias, B-trees, diferentes estruturas de arquivos, etc. Neste sentido, o mdulo deve
suportar variaes nas estruturas a ele associado.

Existncia de rotinas relacionadas


De forma a fazer uma pesquisa em uma tabela, deve-se saber como esta criada, como os elementos
podem ser inseridos, retirados, etc. Deste modo, uma rotina de busca no por si s suficiente; h a
necessidade do acoplamento de diversas rotinas primitivas e relacionadas entre si.

Independncia de representao
Uma estrutura modular verdadeiramente flexvel habilita seus clientes uma operao sem o
conhecimento de modo pelo qual o mdulo foi implementado. Por exemplo, deve ser possvel ao cliente
escrever a seguinte chamada para a busca de x:
esta_presente = BUSCA( x, T );
sem saber qual o tipo da tabela T no momento da chamada. Se vrios algoritmos de busca foram
implementados, os mecanismos internos do mdulo so responsveis de saber qual o apropriado sem a
interveno do cliente. De maneira simplificada, isto uma extenso do princpio de ocultao de
informao pois havendo a necessidade de mudana na implementao, os clientes esto protegidos.
No entanto, a idia vai mais alm. O princpio da independncia de representao no significa
somente que mudanas na representao devem ser invisveis para os clientes durante o ciclo de
desenvolvimento do sistema: os clientes devem ser imunes tambm a mudanas durante a execuo. No
exemplo acima, desejvel que a rotina BUSCA se adapte automaticamente para a forma de T em tempo
de execuo mesmo que esta forma tenha sido alterada do instante da ltima chamada.
Este requisito importante no somente pela questo do reuso mas tambm pela extensibilidade. Se T
pode mudar de forma em tempo de execuo, ento uma deciso no sistema deve ser tomada para a
utilizao da verso de BUSCA correta. Em outras palavras, se no houver esse mecanismo automtico o
cdigo, em algum local, deve conter um controle do tipo:
se T do tipo A ento "mecanismo A"
se T do tipo B ento "mecanismo B"
se T do tipo C ento "mecanismo C"
A estrutura de deciso deve estar ou no mdulo ou no cliente. Ambos os casos no so satisfatrios.
Se a deciso estiver no mdulo, este mdulo deve saber sobre todos as possibilidades existentes. Tal
poltica pode levar a construo de mdulos difceis de gerenciar e sujeitos a constantes manutenes.
Deixar a deciso para o cliente no melhor. Desta forma, o cliente obrigado a especificar que T uma
tabela do tipo A, B, etc. mas no requerido a dizer mais nada: esta informao suficiente para
determinar que variante de BUSCA deve ser utilizada.
A soluo para o problema introduzida pelas linguagens orientadas por objetos com o mecanismo de
herana em que o desenvolvimento feito atravs da descentralizao da arquitetura modular. Esta
construda por sucessivos incrementos e modificaes conectadas por relaes bem definidas que definem
as verses corretas de BUSCA. Este mecanismo chamado de Amarrao Dinmica (Late-Binding ou
Dynamic binding ).

Semelhanas nos subcasos


Este ltimo item em reuso afeta o design e a construo dos mdulos e no seus clientes. Este
fundamental pois determina a possibilidade da construo de mdulos bem construdos sem repeties
indesejveis. O aparecimento de repeties excessivas nos mdulos compromete suas consistncias
internas e torna-os difceis de fazer manuteno.
O problema surge como os programadores podem se aproveitar e tomar vantagem de semelhanas em
subcasos. Para tal, deve existir no conjunto de possibilidades de implementao subgrupos de solues
com a mesma estrutura. No exemplo de busca na tabela, um exemplo tpico o aparecimento de
implementaes relacionadas com tabelas seqenciais. Neste caso, o algoritmo pode ser descrito da
mesma forma para todos os subcasos diferindo apenas em um conjunto reduzido de rotinas primitivas. A
figura abaixo ilustra o algoritmo seqencial genrico e algumas implementaes de operaes primitivas.

int BUSCA( Elemento x; TabelaSequencial T )


{
int Pos
COMEA();
while not FINAL() && not ACHOU( pos, x, T ) do
MOVE_PROXIMO();
return not FINAL();
}

Vetor

Lista Encadeada

COMEA()

i := 1

MOVE_PROXIMO
()
FINAL()

i := i +
1
i >
tamanho

l :=
ponta_lista
l := l.next

Arquivo
Seq.
rewind(
)
read()

l == NULL

eof()

Neste caso, evita-se a repetio do mtodo de BUSCA nas implementaes de busca seqencial. Temse uma nica funo de pesquisa que se difere em quais funoes especficas de COMEA,
MOVE_PRXIMO e FINAL sero chamadas.
Os mecanismos decritos acima so compreendidos nas linguagens orientadas por objetos pelo
mecanismo de herana descrito a seguir.

Herana
Provavelmente herana o recurso que torna o conceito de classe mais poderoso. Em C++, o termo
herana se aplica apenas s classes. Variveis no podem herdar de outras variveis e funes no podem
herdar de outras funes.
Herana permite que se construa e estenda continuamente classes desenvolvidas por voc mesmo ou
por outras pessoas, sem nenhum limite. Comeando da classe mais simples, pode-se derivar classes cada
vez mais complexas que no so apenas mais fceis de debuggar, mas elas prprias so mais simples.
O objetivo de um projeto em C++ desenvolver classes que resolvam um determinado problema.
Estas classes so geralmente construdas incrementalmente comeando de uma classe bsica simples,
atravs de herana. Cada vez que se deriva uma nova classe comeando de uma j existente, pode-se
herdar algumas ou todas as caractersticas da classe pai, adicionando novas quando for necessrio. Um
projeto completo pode ter centenas de classes, mas normalmente estas classes so derivadas de algumas
poucas classes bsicas. C++ permite no apenas herana simples, mas tambm mltipla, permitindo que
uma classe incorpore comportamentos de todas as suas classes bases.
Reutilizao em C++ se d atravs do uso de uma classe j existente ou da construo de uma nova
classe a partir de uma j existente.

Classes derivadas
A descrio anterior pode ser interessante, mas um exemplo a melhor forma de mostrar o que
herana e como ela funciona. Aqui est um exemplo de duas classes, a segunda herdando as propriedades
da primeira:
class Caixa {
public:
int altura, largura;
void Altura(int a) { altura=a; }
void Largura(int l) { largura=l; }
};
class CaixaColorida : public Caixa {
public:
int cor;
void Cor(int c) { cor=c; }
};

Usando a terminologia de C++, a classe Caixa chamada classe base para a classe CaixaColorida,
que chamada classe derivada. Classes base so tambm chamadas de classes pai. A classe
CaixaColorida foi declarada com apenas uma funo, mas ela herda duas funes e duas variveis da
classe base. Sendo assim, o seguinte cdigo possvel:
void main()
{
CaixaColorida cc;
cc.Cor(5);
cc.Largura(3); // herdada
cc.Altura(50); // herdada
}
Note que as funes herdadas so udadas exatamente como as no herdadas. A classe Colorida no
precisou sequer mencionar o fato de que as funes Caixa::Altura() e Caixa::Largura() foram herdadas.
Esta uniformidade de expresso um grande recurso de C++. Usar um recurso de uma classe no requer
que se saiba se este recurso foi herdado ou no, j que a notao invariante. Em muitas classes pode
existir uma cadeia de classes base derivadas de outras classes base. Uma classe herdada de uma rvore de
herana como esta herdaria caractersticas de muitas classes pai diferentes. Entretanto, em C++, no
preciso se preocupar onde ou quando um recurso foi introduzido na rvore.
Derivar uma classe de outra aumenta a flexibilidade a um custo baixo. Uma vez que j existe uma
classe base slida, apenas as mudanas feitas nas classes derivadas precisam ser depuradas. Mas quando
exatamente se usa uma classe base, e que tipos de modificaes precisam ser feitas? Quando se herda
caracterstias de uma classe base, a classe derivada pode estender, restringir, modificar, eliminar ou usar
qualquer dos recursos sem qualquer modificao.

O que no herdado
Nem tudo herdado quando se declara uma classe derivada. Alguns casos so inconsistentes com
herana por definio:
Construtores
Destrutores
Operadores new
Operadores de atribuio (=)
Relacionamentos friend
Atributos privados
Classes derivadas invocam o construtor da classe base automaticamente, assim que so instanciadas.

Membros de classes protected


Na seo de controle de acesso, vimos como deixar disponveis ou ocultar atributos das classes,
usando os especificadores public e private. Alm desses dois, existe um outro especificador, protected. Do
ponto de vista de fora da classe, um atributo protected funciona como private: no acessvel fora da
classe; a diferena est na herana. Enquanto um atributo private de uma classe base no visvel na
classe derivada, um protected , e continua sendo protected na classe derivada. Por exemplo:
class A {
private:
int a;
protected:
int b;
public:
int c;
};
class B : public A {
public:
int geta() { return a; }
int getb() { return b; }
int getc() { return c; }
};
void main()

// ERRO!! a no visvel
// vlido (b protected)
// vlido (c public)

{
A ca;
B cb;
ca.a = 1; // ERRO! a no visvel (private)
ca.b = 2; // ERRO! b no visvel de fora (protected)
ca.c = 3; // vlido (c public)
cb.a = 4; // ERRO! a no visvel nem internamente em B
cb.b = 5; // ERRO! b continua protected em B
cb.c = 6; // vlido (c continua public em B)
}

Construtores e destrutores
Quando uma classe instanciada, seu construtor chamado. Se a classe foi derivada de alguma outra,
o construtor da classe base tambm precisa ser chamado. A ordem de chamada dos construtores fixa em
C++. Primeiro a classe base construda, para depois a derivada ser construda. Se a classe base tambm
deriva de alguma outra, o processo se repete recursivamente at que uma classe no derivada alcanada.
Desta forma, quando um construtor para uma classe derivada chamado, todos os procedimentos
efetuados pelo construtor da classe base j foram realizados. Considere a seguinte rvore de herana:
class Primeira {};
class Segunda: public Primeira {};
class Terceira: public Segunda {};
Quando a classe Terceira instanciada, os construtores so chamados da seguinte maneira:
Primeira::Primeira();
Segunda::Segunda();
Terceira::Terceira();
Esta ordem faz sentido, j que uma classe derivada uma especializao de uma classe mais genrica.
Isto significa que o construtor de uma classe derivada pode usar atributos herdados.
Os destrutores so chamados na ordem inversa dos construtores. Primeiro, os atributos mais
especializados so destrudos, depois os mais gerais. Ento a ordem de chamada dos destrutores quando
Terceira sai do escopo :
Terceira::~Terceira();
Segunda::~Segunda();
Primeira::~Primeira();
Como os construtores das classes base so chamados automaticamente, deve existir alguma maneira
de passar os argumentos corretos para estes construtores, no caso de eles necessitarem de parmetros.
Existe uma notao especial para este caso, ilustrada abaixo, para funes inline e no inline:
class Primeira {
int a, b, c;
public:
Primeira(int x, int y, int z) { a=x; b=y; c=z; }
};
class Segunda : public Primeira {
int valor;
public:
Segunda(int d) : Primeira(d, d+1, d+2) { valor = d; }
Segunda(int d, int e);
};
Segunda::Segunda(int d, int e) : Primeira(d, e, 13)
{
valor = d + e;
}

A partir do exemplo acima, no difcil perceber que, se uma classe base no possui um construtor
sem parmetros, a classe derivada tem que, obrigatoriamente, declarar um construtor, mesmo que este
construtor seja vazio:
class Base {
protected:
int valor;
public:
Base(int a) { valor = a; }
// esta classe no possui um construtor
// sem parmetros
};
class DerivadaErrada : public Base{
public:
int pegaValor() { return valor; }
// ERRO! classe no declarou construtor, compilador no
// sabe que parmetro passar para Base
};
class DerivadaCerta: public Base {
public:
int pegaValor() { return valor; }
DerivadaCerta() : Base(0) {}
// CERTO: mesmo que no haja nada a fazer
// para inicializar a classe,
// necessrio declarar um construtor
// para dizer com que parmetro
// construir a classe Base
};

Herana pblica x herana privada


Nos exemplos acima, em toda declarao de uma classe derivada, usou-se a palavra public:
class B : public A { ...
Na realidade, os especificadores de acesso private e public podem ser usados na declarao de uma
herana. Por default, as heranas so private, por isso usou-se public nos exemplos acima.
Estes especificadores afetam o nvel de acesso que os atributos tero na classe derivada:
private: todos os atributos herdados (public, protected) tornam-se private na classe derivada;
public: todos os atributos public so public na classe derivada, e todos os protected tambm
continuam protected.
Na realidade, isto uma consequncia da finalidade real de heranas public e protected, que voltar a
ser discutida em compatibilidade de tipos.

Exerccio 9 - Calculadora como um objeto. Classe RPN.


Alterar a calculadora para transformar a prpria calculadora em um objeto

Captulo 6
Polimorfismo
A origem da palavra polimorfismo vem do grego: poli (muitos) e morphos (forma) - mltiplas formas.
Polimorfismo descreve a capacidade de um cdigo C++ se comportar de diferentes formas dependendo do
contexto em tempo de execuo.
Este um dos recursos mais poderosos de linguagens orientadas a objetos (se no o mais), que permite
trabalhar em um nvel de abstrao bem alto ao mesmo tempo que facilita a incorporao de novos pedaos
em um sistema j existente. Em C++ o polimorfismo se d atravs da converso de ponteiros (ou referncias)
para objetos.

Converso de ponteiros
Normalmente se usam no objetos de classes isoladas, mas sim objetos em uma hierarquia de classes.
Considere as seguintes classes:
class A {
public: void f();
};
class B: public A {
public: void g();
};
Como B derivado de A, todos os membros disponveis em A (funo f) tambm estaro disponveis em
B. Ento B um superconjunto de A, e todas as operaes que podem ser feitas com objetos da classe A
tambm podem ser feitas com objetos do tipo B. A classe B uma especializao da classe A, e no s um
objeto do tipo B, mas tambm um objeto do tipo A. Nada impede que objetos da classe B sejam vistos como
sendo da classe A, pois todas as operaes vlidas para A so tambm vlidas para B. Ver um objeto do tipo B
como sendo do tipo A significa convert-lo para o tipo A. Esta converso pode ser feita, sempre no sentido da
classe mais especializada para a mais bsica. A converso inversa no permitida, pois operaes especficas
de B no so vlidas sobre objetos da classe A. Converso aqui no deve ser entendida como cpia. A simples
atribuio de um objeto do tipo B para um objeto do tipo A copia a parte A do objeto do tipo B para o objeto
do tipo A. O polimorfismo feito atravs da converso de ponteiros. O exemplo abaixo mostra as vrias
alternativas:
void main()
{
A a, *pa; // pa pode apontar para objetos do tipo A e derivados
B b, *pb; // pb pode apontar para objetos do tipo B e derivados
a = b;
b = a;
pa = &a;
pa = &b;
pb = pa;
pb = &b;
pb = &a;

//
//
//
//
//
//
//

copia a parte A de b para a (no converso)


erro! a pode no ter todos elementos para a cpia
ok
ok, pa aponta para um objeto do tipo B
erro! pa pode apontar para um objeto do tipo A
ok
erro! pb no pode apontar para objetos do tipo A

}
Para tirar qualquer dvida sobre quais converses podem ser feitas, o exemplo abaixo mostra o que pode
ser feito com este recurso a partir das classes A e B:
void chamaf(A* a) // pode ser chamada para A e derivados
{
a->f();
}

void chamag(B* b) // pode ser chamada para B e derivados


{
b->g();
}
void main()
{
A a;
B b;
chamaf(&a); // ok, a tem a
chamag(&a); // erro! a no
// (a no pode
chamaf(&b); // ok, b tem a
chamag(&b); // ok, b tem a
}

funo f
tem a funo g
ser convertido para o tipo B)
funo f
funo g

Repare que as funes chamaf e chamag foram escritas para os tipos A e B, mas podem ser usadas com
qualquer objeto que seja derivado destes. Se um novo objeto derivado de B for criado no futuro, a mesma
funo poder ser usada sem necessidade de recompilao.
Estas converses s podem ser feitas quando a herana pblica. Se a herana for privada a converso
no permitida.

Redefinio de mtodos em uma hierarquia


No existe sobrecarga em uma hierarquia. A definio de um mtodo com mesmo nome de uma classe
bsica no deixa os dois disponveis, mesmo que os tipos dos parmetros sejam diferentes. Os mtodos da
classe bsica de mesmo nome so escondidos. Eles no ficam inacessveis, mas no podem ser chamados
diretamente:
class A {
public: void f();
};
class B : public A {
public:
void f(int a);
// f(int) esconde f()
void f(char* str);
};
void main()
{
B b;
b.f(10);
b.f("abc");
b.f();
b.A::f();
}

//
//
//
//

ok, funo f(int) de B


ok, funo f(char*) de B
erro! f(int) escondeu f()
ok

possvel tambm declarar um mtodo com mesmos nome e assinatura (tipo de retorno e tipo dos
parmetros) que um da classe base. O novo mtodo esconde o da classe base, que precisa do operador de
escopo para ser acessado. No entanto, esta redefinio merece ateno especial. Considerando o exemplo:
class A {
public: void f();
};
class B : public A {
public: void f();
};

void chamaf(A* a) { a->f(); }


void main()
{
B b;
chamaf(&b);
}
A funo chamaf pode ser usada para qualquer objeto do tipo A e derivados. No exemplo acima, ela
chamada com um objeto do tipo B. O mtodo f chamado no corpo de chamaf. Mas qual verso ser
executada? No exemplo acima o mtodo executado ser A::f.
Isto o que acontece, mas ser que este o comportamento desejado? Se o polimorfismo nesse caso for
encarado como uma maneira diferente (mais limitada) de ver o mesmo objeto, no seria natural chamar o
mtodo B::f? Afinal, o objeto do tipo B, apenas est guardado em um ponteiro para o tipo A. O
comportamento ideal pode no estar claro agora. A seo seguinte procura esclarecer este ponto.

Exemplo: classes List e ListN


A classe abaixo implementa uma lista encadeada:
class List {
public:
List();
int add(int);
// retorna 0 em caso de erro, 1 ok
int remove(int); // retorna 0 em caso de erro, 1 ok
};
Suponha que esta declarao parte de uma biblioteca cujo cdigo fonte no est disponvel. Ou seja, a
declarao acima faz parte de um header file a ser includo em arquivos que utilizem a biblioteca.
Atravs do mecanismo de herana, possvel criar uma nova lista que retorna o nmero de elementos
contidos na lista:
class ListN : public List {
int n;
public:
ListN() { n=0; }
int nelems() { return n; }
int add(int i)
{
int r = List::add(i);
if (r) n++;
return r;
}
int remove(int i)
{
int r = List::remove(i);
if (r) n--;
return r;
}
};
A nova classe foi criada sem nenhum conhecimento sobre a implementao da classe List.
Para completar o exemplo, falta utilizar as declaraes acima:
void manipula_lista(List* l)
{
// insere e remove vrios elementos na lista
}

void main()
{
List l1;
ListN l2;
manipula_lista(&l1);
manipula_lista(&l2);
printf("a lista l2 contm %d elementos\n", l2.nelems());
}
A funo manipula_lista utiliza apenas os mtodos add e remove, portanto pode operar tanto sobre objetos
do tipo List quanto do tipo ListN. Supondo que esta funo retorna deixando cinco elementos na lista, qual
ser o resultado do printf? Assim como na seo anterior, as funes chamadas em manipula_lista sero
List::add e List::remove. Como estas funes no alteram a varivel n, o resultado do printf ser:
a lista l2 contm 0 elementos
Ou seja, a manipulao deixou o objeto l2 inconsistente internamente. Para manter a sua consistncia,
seria preciso que os mtodos ListN::add e ListN::remove fossem chamados, o que no est acontecendo.

Early x late binding


Nos exemplos acima est acontecendo o que se chama de early-binding. Early-binding a ligao dos
identificadores em tempo de compilao. Quando a funo manipula_lista foi compilada, o cdigo gerado
tem uma chamada explcita funo A::f. Com isso, qualquer que seja o objeto passado como parmetro, a
funo chamada ser sempre a mesma. Este o processo de ligao utilizado em linguagens de programao
convencionais, como C e Pascal.
O problema de early-binding que o programador precisa saber que objetos sero usados em todas as
chamadas de funo em todas as situaes. Isto uma limitao. A vantagem a eficincia; a chamada
funo feita diretamente, pois o cdigo gerado tem o endereo fsico da mesma.
Late-binding um tipo de ligao que deixa a amarrao dos nomes para ser feita durante a execuo do
programa. Isto , dependendo da situao a amarrao pode ser feita a funes diferentes durante a execuo
do programa. exatamente o que falta no exemplo da lista encadeada. A funo manipula_lista deve chamar
ora List::add ora ListN:add, dependendo do tipo do objeto.
O problema de late-binding exatamente a eficincia. O cdigo deve descobrir que funo chamar durante
a execuo do programa. Linguagens orientadas a objetos puras, como Smalltalk, usam exclusivamente latebinding. O resultado uma linguagem extremamente poderosa, mas com algumas penalidades em relao ao
tempo de execuo. Por outro lado, ANSI C usa somente early-binding, resultando em alta velocidade mas
falta de flexibilidade.
C++ no uma linguagem procedural tradicional como Pascal, mas tambm no uma linguagem
orientada a objetos pura. C++ uma linguagem hbrida. C++ usa early e late binding procurando oferecer o
melhor de cada um dos mtodos. O programador controla quando usar um ou outro. Para um cdigo em que a
execuo determinstica, pode-se forar C++ para usar early-binding. Em situaes mais complexas, usa-se
late-binding. Desta forma, pode-se conciliar alta velocidade com flexibilidade.

Mtodos virtuais
Em C++, late-binding especificado declarando-se um mtodo como virtual. Late-binding s faz sentido
para objetos que fazem parte de uma hierarquia de classes. Se um mtodo f declarado virtual em uma classe
Base e redefinido na classe Derivada, qualquer chamada a f sobre um objeto do tipo Derivada, mesmo que
via um ponteiro para Base, executar Derivada::f. A redefinio de um mtodo virtual tambm virtual. A
especificao virtual nesse caso redundante.
Este mecanismo pode ser usado com as listas encadeadas para manter a consistncia dos objetos do tipo
ListN. Basta declarar, na classe List, os mtodos add e remove como virtuais:
class List {
public:
List();

virtual int add(int);


// retorna 0 em caso de erro, 1 ok
virtual int remove(int); // retorna 0 em caso de erro, 1 ok
};
Agora a funo manipula_lista executar as verses corretas de add e remove.

Destrutores virtuais
No faz sentido construtores poderem ser virtuais, j que eles no so chamados a partir de um objeto, mas
sim para criar objetos. Destrutores, apesar de serem mtodos especiais, podem ser virtuais porque so
chamados a partir de um objeto. A chamada se d em duas situaes: quando o objeto sai do escopo e quando
ele destrudo com delete. Na primeira situao, o compilador sabe o tipo exato do objeto e portanto chama o
destrutor correto. J na segunda situao isso pode no ocorrer, como mostra o exemplo abaixo:
class A {
// ...
};
class B : public A {
int* p;
public:
B(int size) { p = new int[size]; }
~B() { delete [] p; }
// ...
};
void destroy(A* a)
{
delete a; // chama destrutor
}
void main()
{
B* b = new B(20);
destroy(b);
}
O que est acontecendo exatamante o que acontecia na primeira verso das listas encadeadas. A funo
destroy chama (com early-binding) diretamente o destrutor A::~A, quando o certo seria chamar B::~B antes,
para depois chamar A::~A. Para forar este funcionamento correto, preciso que o destrutor seja virtual
(virtual ~A()). Caso contrrio, objetos podem ser destrudos e deixando alguma coisa para trs.

Tabelas virtuais
Esta seo tem por objetivo esclarecer um pouco o que acontece por trs dos mtodos virtuais, ou seja,
como late-binding implementado. Este conhecimento ajuda o entendimento dos mtodos virtuais, suas
limitaes, poderios e eficincia.
Imaginando a seguinte hierarquia de classes:
class A {
int a;
public:
virtual void f();
virtual void g();
};
class B : public A {
int b;
public:

virtual void f(); // redefinio de A::f


virtual void h();
};
void chamaf(A *a) { a->f(); }
A funo chamaf executar o mtodo f do objeto passado como parmetro. Dependendo do tipo do objeto,
a mesma linha executar A::f ou B::f. Ou seja, como se a funo chamaf fosse implementada internamente
assim:
void chamaf(A *a)
{
switch (tipo de a)
{
case A: a->A::f(); break;
case B: a->B::f(); break;
}
}
Mas isto significa que cada classe derivada de A precisa de um case neste switch, o que tem vrias
desvantagens: esta funo no poderia ser utilizada com novas classes que venham a ser criadas no futuro;
quanto mais classes na hierarquia pior seria a eficincia e, acima de tudo, o compilador no tem como saber
todas as classes derivadas de A. Esta anlise mostra que esta implementao irreal, e outra estratgia deve
ser usada.
Para resolver este problema, C++ utiliza tabelas virtuais, que so descries dos mtodos de uma
determinada classe. Estas tabelas so vetores de funes. O nmero de entradas da tabela igual ao nmero
de mtodos virtuais da classe e cada posio da tabela tem o ponteiro para uma funo virtual. Quando uma
classe tem algum mtodo virtual, todos os objetos desta classe tero uma referncia para esta tabela, que o
descritor da classe.
No exemplo acima, existem duas tabelas virtuais: uma para a classe A e outra para B. Quando um objeto
criado, ele leva uma referncia para esta tabela. A tabela de A tem duas posies, uma para cada mtodo
virtual (f e g). A tabela de B tem uma posio a mais para o mtodo h. A posio dos mtodos na tabela
sempre a mesma, ou seja, se na tabela de A a primeira posio apontar para o mtodo f, em todas as classes
derivadas a primeira posio ser de f. Na tabela de A, este ponteiro aponta para A::f, enquanto que em B ele
aponta para B::f. Quando acontece alguma chamada no cdigo, a funo no chamada pelo nome, e sim por
indexao a esta tabela. Em qualquer tabela de classes derivadas de A o mtodo f estar na mesma posio, no
caso, a primeira. A figura abaixo mostra as possveis tabelas e objetos para o exemplo de A e B com alguns
objetos na memria:

a1
vtable
a = 666
b1
vtable
a = 10
b = 20

b2
vtable
a = 65
b=7

Tabela de A
A::f
A::g

Tabela de B
B::f
A::g
B::h

Considerando que a tabela virtual um campo de todos objetos (com nome vtable por exemplo), a funo
chamaf pode ser implementada assim:
void chamaf(A *a)
{
a->vtable[0](); // posio 0 corresponde ao mtodo f
}

Esta implementao funcionar com qualquer objeto derivado de A, at mesmo de classes futuras, pois
no exige que o compilador saiba quais so as classes existentes. A eficincia sempre a mesma
independente de quantas classes existam ou qual a sua altura na rvore de hierarquia. Esta eficincia no a
mesma de uma chamada de funo normal (com early-binding) pois envolve uma indireo.

Classes abstratas - Mtodos virtuais nulos


O mecanismo de polimorfismo apresentado at aqui permite a especializao de objetos mantendo sua
consistncia e sem invalidar cdigos que manipulem objetos mais bsicos. Esta ltima caracterstica permite a
construo de um sistema inteiro a partir de poucas classes bsicas, independente das especializaes que
vierem a ser feitas.
Se um sistema exige um pilha por exemplo, possvel implementar uma limitada (com vetores por
exemplo) e contruir todo o sistema em cima desta. Depois do sistema pronto, pode-se alterar a pilha para uma
implementao mais sofisticada (listas encadeadas) sem necessidade de alterar todo o cdigo que manipula a
pilha. Basta que a nova pilha seja uma subclasse da pilha original.
Na realidade nem necessrio que a pilha original seja implementada, j que no futuro ela ser
substituda. Com os recursos vistos at aqui, deve haver uma implementao para todos os mtodos da classe,
mas C++ permite a declarao de classes sem implementao de alguns mtodos. Mtodos sem
implementao so sempre virtuais e so chamados virtuais puros. Uma classe que tem pelo menos um
mtodo virtual puro chamada de abstrata.
No permitido criar objetos de classes abstratas. Estas so feitas apenas para a criao de outras por
herana. Classes derivadas de classes abstratas continuam sendo abstratas, a no ser que forneam
implementao para todos os mtodo virtuais puros.
Classes abstratas esto em um nvel intermedirio entre especificao e programa. Uma classe abstrata
quase uma especificao; ao mesmo tempo um elemento da linguagem de programao. Estas classes
permitem a definio das interfaces dos objetos sem entrar em detalhes de implementao. A partir desta
descrio j possvel implementar programas que manipulem estas classes pelo mecanismo de
polimorfismo.
Uma aplicao que use uma pilha pode ser totalmente implementada a partir de uma classe abstrata que
descreva uma pilha:
class Stack {
public:
virtual void push(int) = 0;
virtual int pop() = 0;
virtual int empty() = 0;
};
Apenas com esta declarao j possvel manipular pilhas. Qualquer objeto derivado deste pode ser usado
onde se espera um Stack. A nica restrio a criao das pilhas. Para criar uma pilha real, preciso declarar
uma classe no abstrata derivada de Stack. No abstrata significa com implementao dos trs mtodos de
Stack.

Herana de tipo x herana de cdigo


Vamos supor que chegada a hora de implementar a pilha a ser usada na aplicao da seo anterior. Para
que os objetos da nova classe possam ser utilizados na a aplicao, a pilha deve herdar de Stack:
class StackVector : public Stack { ... };
Agora vamos imaginar outra situao. preciso implementar uma classe Queue (fila). Para facilitar esta
tarefa, ser usada uma classe LinkedList, que implementa uma lista encadeada. A especificao da fila inclui
apenas os mtodos insert, remove e empty. A declarao da classe fica assim:
class Queue: private LinkedList {
public:
void insert(int i);
int remove();

int

empty();

};
Repare que, para a criao da fila, a herana usada foi a privada. Isto evita que os mtodos da lista
encadeada possam ser usados diretamente sobre a fila, o que seria um furo na consistncia do objeto.
Estas duas situaes tem mais diferenas do que uma simples herana pblica ou privada.
Conceitualmente estas duas heranas esto sendo feitas com objetivos totalmente diferentes. No primeiro
caso, o objetivo da herana simplesmente permitir a utilizao da classe StackVector como uma Stack. Ou
seja, aplicaes que manipulem Stacks tambm podem ser usadas para manipular StackVectors. Nesse caso a
herana s feita para que a classe derivada tenha o tipo da classe base. a herana de tipo.
No segundo caso, o objetivo no usar objetos do tipo Queue em cdigos que manipulem LinkedList. A
herana est sendo usada para reutilizar o cdigo escrito para implementar listas encadeadas, evitando a reimplementao do que j est pronto. a herana de cdigo.
Normalmente a herana privada se aplica a uma herana de cdigo, enquanto que a pblica se aplica
herana de tipo. Esta diferenciao importante na hora de projetar sistemas.

Exemplo: rvore binria para qualquer objeto


Para exemplificar um uso de classes abstratas, esta seo implementa uma rvore binria simples. A
primeira verso armazena floats na sua estrutura:
class BinTree {
struct elem {
elem* right;
elem* left;
float val;
elem(float f) { val=f; right=left=0; }
} *root;
int look(elem* no, float f)
{
if (!no) return 0;
if (no->val == f) return 1;
if (no->val > f) return look(no->left, f);
return look(no->right, f);
}
void put(elem*& no, float f)
{
if (!no) no = new elem(f);
else if (no->val > f) put(no->left, f);
else put(no->right, f);
}
public:
BinTree() { root = 0; }
int find(float v) { return look(root, v); }
void insert(float v) { put(root, v); }
};
Analisando esta classe, chega-se concluso de que a rvore binria em si tem sempre o mesmo
comportamento independente do tipo de dado que ela armazena. O tipo float s aparece porque este tipo tem
que ser declarado. Se fosse necessria uma rvore binria para armazenar inteiros, a implementao seria
exatamente a mesma. A nica modificao necessria seria a substituio de todas as palavras float por int.
Com o uso de classes abstratas possvel fazer uma nica implementao da rvore binria servir para
vrios tipos de dados. A idia criar uma classe abstrata que represente o tipo de dado a ser armazenado.
Qualquer classe derivada desta pode ser usada com a rvore. A implementao da rvore passa a manipular
objetos desta classe abstrata. Analisando a implementao acima, nota-se que, para que um tipo de dado seja
includo na rvore binria, ele precisa ter as operaes de comparao > e ==. Isto define a classe abstrata:

class BinTreeObj {
public:
virtual int operator==(BinTreeObj&) = 0;
virtual int operator> (BinTreeObj&) = 0;
};
A declarao destes mtodos virtuais puros implica que qualquer tipo derivado deste precisa fornecer uma
implementao para estes mtodos para deixar de ser abstrato. A verso modificada da rvore fica assim:
class BinTree {
struct elem {
elem* right;
elem* left;
BinTreeObj& val;
elem(BinTreeObj& f) : val(f) { right=left=0; }
} *root;
int look(elem* no, BinTreeObj& f)
{
if (!no) return 0;
if (no->val == f) return 1;
if (no->val > f) return look(no->left, f);
return look(no->right, f);
}
void put(elem*& no, BinTreeObj& f)
{
if (!no) no = new elem(f);
else if (no->val > f) put(no->left, f);
else put(no->right, f);
}
public:
BinTree() { root = 0; }
int find(BinTreeObj& v) { return look(root, v); }
void insert(BinTreeObj& v) { put(root, v); }
};
Como o polimorfismo se d pela converso de ponteiros, a rvore deve armazenar apenas ponteiros ou
referncias. Para utilizar esta rvore, basta fazer com que o tipo dos objetos a serem armazenados herdem da
classe BinTreeObj. Para armazenar algum tipo primitivo necessrio criar uma nova classe derivada de
BinTreeObj que armazene este tipo.

Converso de um objeto bsico para um derivado


Aproveitando a rvore binria da seo anterior, uma classe String ser criada com o objetivo de
armazenar o tipo char* na rvore. A classe deve herdar de BinTreeObj e declarar os dois mtodos de
comparao. Algo do tipo:
class String : public BinTreeObj {
char *str;
public:
String(char* s) { str = s; }
int operator==(String& o)
{ return !strcmp(str, o.str); }
int operator> (String& o)
{ return strcmp(str, o.str) > 0; }
};
No entanto, com a declarao acima, o compilador reclama que a classe String continua abstrata quando
algum objeto criado:

void main()
{
BinTree bt;
bt.insert( * new String("abc") ); // ERRO! String abstrata
}
O problema que os mtodos declarados em String no esto redefinindo os da classe abstrata, mas
escondendo-os. Analisando a assinatura dos dois mtodos, percebe-se a diferena:
int BinTreeObj::operator==(BinTreeObj&);
int String
::operator==(String& o)
Se fosse o mtodo de String estivesse redefinindo o de BinTreeObj, seria possvel converter objetos de
uma classe mais bsica para uma mais especfica. Como foi visto com polimorfismo, esta no uma
converso segura. Na implementao da rvore binria, este mtodo chamado e o parmetro sempre uma
referncia para um BinTreeObj, que pode no ser uma String. Para redefinir um mtodo, os parmetros devem
ser ou do mesmo tipo do declarado na classe base ou de um tipo mais bsico. Nunca de um tipo derivado. O
contrrio acontece com o tipo de retorno de uma funo.
Chegando concluso de que os parmetros dos mtodos de String devem ser do tipo BinTreeObj, surge
outro problema. Como acessar o campo str e fazer a comparao das Strings? Deve haver alguma maneira de
converter um BinTreeObj em uma String, mesmo que esta converso no seja segura. Na realidade esta
converso pode ser forda atravs de type casts explcitos, assim como em C possvel converter um ponteiro
para outro de um tipo completamente diferente. Se o objeto for realmente do tipo desejado, a converso
feita. Mas se o tipo no for o esperado, os resultados so imprevisveis. Mas nesse caso, a nica opo:
class String : public BinTreeObj {
char *str;
public:
String(char* s) { str = s; }
int operator==(BinTreeObj& o)
{ return !strcmp(str, ((String&)o).str); }
int operator> (BinTreeObj& o)
{ return strcmp(str, ((String&)o).str) > 0; }
};
Esta com certeza no a melhor maneira de se implementar uma classe que manipula um tipo qualquer.
Existem duas outras maneiras seguras, vistas mais frente: templates e type casts dinmicos.

Herana mltipla
Em C++, a herana no se limita a uma nica classe base. Uma classe pode ter vrios pais, herdando
caractersticas de todos eles. Este tipo de herana introduz grande dose de complexidade na linguagem e no
compilador, mas os benefcios so substanciais. Considere a criao de uma classe MesaRedonda, tendo no
s propriedades de mesas, mas tambm as caractersticas geomtricas de ser redonda. O cdigo abaixo uma
possvel implementao:
class Circulo {
float raio;
public:
Circulo(float r) { raio = r; }
float area()
{ return raio*raio*3.14159; }
};
class Mesa {
float ipeso;
float ialtura;
public:
Mesa(float p, float a) { ipeso = p; ialtura=a; }
float peso()
{ return ipeso; }
float altura() { return ialtura; }

};
class MesaRedonda: public Circulo, public Mesa {
int icor;
public:
MesaRedonda(int c, float a, float p, float r);
int cor() { return icor; }
};
MesaRedonda::MesaRedonda(int c, float a, float p, float r)
: Mesa(p, a), Circulo(r)
{
icor = c;
}
void main()
{
MesaRedonda mesa(5, 1,
printf("Peso:
%f\n",
printf("Altura: %f\n",
printf("Area:
%f\n",
printf("Cor:
%d\n",
};

20, 3.5 );
mesa.peso());
mesa.altura());
mesa.area());
mesa.cor());

Um exemplo natural poderia sair da seo que discute herana de tipo ou cdigo. Para implementar um
pilha com listas encadeadas que possa ser usada como Stack e aproveitando uma classe j implementada de
listas encadeadas, a declarao seria assim:
class StackList : public Stack, private LinkedList {
// ...
};

Ordem de chamada dos construtores e destrutores


Assim como em herana simples, os construtores das classes base so chamados antes do construtor da
classe derivada. A ordem de declarao na classe define a ordem de chamada dos construtores. No exemplo
acima, a classe foi declarada com uma ordem
class MesaRedonda: public Circulo, public Mesa {
e o construtor com outra:
MesaRedonda::MesaRedonda(int c, float a, float p, float r)
: Mesa(p, a), Circulo(r)
Como a ordem de declarao na classe a que define, a ordem dos construtores ser:
Circulo::Circulo
Mesa::Mesa
MesaRedonda::MesaRedonda

Classes bsicas virtuais


Classes base virtuais s so usadas com herana mltipla. uma maneira de o programador controlar
como as classes devem ser herdadas. Por exemplo:
class A {
public:
int a;
};

class B: public A {};


class C: public A{};
class D: public B, public C {
public:
int valor() { return a; }
};
Este cdigo gera uma hierarquia onde a classe D tem duas cpias da parte A, uma associada a B e outra a
C. Portanto, o cdigo acima gera um erro de compilao:
Member is ambiguous: 'A::a' and 'A::a'
O problema que a declarao de B herdando de A faz com que a classe B j tenha uma parte A
incorporada. Nesse caso, B j tem a sua varivel a. O mesmo acontece com C. O resultado so duas cpias de
A na hierarquia. Uma a parte Ade B e outra a parte Ade C. O compilador no sabe que cpia de a esta
sendo referenciada. O operador de escopo poderia ser utilizado para retirar o erro:
int valor() { return C::a; }
s vezes o programador quer montar uma rvore onde s exista uma cpia de A. o caso de usar uma
classe base virtual. Declarando uma classe base como virtual faz com que a classe derivada no inclua a
classe base. Seria o caso de declarar as heranas de B e C como virtuais, assim nenhuma das duas teria uma
parte A:
class B: public virtual A {};
class C: public virtual A{};
class D: public B, public C {
public:
int valor() { return a; }
};
Agora a funo valor no precisa mais do operador de escopo, e a rvore gerada ter apenas uma cpia de
A.

Chamada de construtores de classes bsicas virtuais


Como na herana virtual as classes derivadas no contm a parte da sua classe base, preciso tomar
alguns cuidados na hora de inicializar estas classes bsicas. Supondo que a classe A tenha um construtor que
receba um inteiro:
class A {
public:
int a;
A(int) {}
};
A chamada a este construtor tem que estar explcita no cdigo de B e C:
class B: public virtual A { public: B() : A(1) {} };
class C: public virtual A { public: C() : A(2) {} };
class D: public B, public C {
public:
int valor() { return a; }
// erro! compilador no gera construtor vazio
};
Se um objeto da classe B for criado, sua parte A ser inicializada com 1. Se for criado um do tipo C, a
inicializao ser com 2. E se o objeto for do tipo D? Como a parte A criada diretamente por D (herana
virtual), o prprio construtor de D deve chamar o de A diretamente:
class D: public B, public C {
public:
int valor() { return a; }

D(): A(3) {}
};

Exerccio 10 - Classe List


Implementar uma lista que possa ser utilizada para armazenar qualquer objeto.
A lista s precisa ter um mtodo para insero de objetos. Alm da classe lista, deve ser implementado um
mecanismo para percorrer uma lista em dois sentidos: do incio para o fim e do fim para o incio. Isto no
significa que a lista deve ser ordenada, apenas que ela preserva a ordem utilizada na insero dos elementos.
Uma maneira de preservar esta ordem sempre inserir um elemento novo no incio da lista.

Captulo 7
Programao orientada a eventos
A programao tradicional
No passado, um sistema computacional era composto por uma CPU responsvel por todo o
processamento (mestre) e uma srie de perifricos (escravos), responsveis pela entrada e sada de dados.
Neste paradigma, o usurio assumia uma posio de perifrico, j que ele era somente um mecanismo
de entrada de dados.
O custo/benefcio de uma CPU era extremamente superior ao de qualquer equipamento ou (custo de
trabalho) usurio a ela conectados. Consequentemente, todos os sistemas davam prioridade mxima ao
processamento, em relao entrada e sada de dados.
Com isso, era comum encontrarmos sistemas em que a fatia de tempo dedicada aos perifricos era
sensivelmente menor do que a dedicada CPU.
Esta diferena de custos implicava uma menor dedicao, por parte dos programadores, aos
perifricos e usurios em geral.
Ao colocar o usurio como escravo, este paradigma prejudicava a interao direta com o sistema.
O usurio no tinha como realizar de forma gil suas operaes, j que o sistema no priorizava a
entrada e a sada de informaes.
O dilogo dos usurios com o sistema foi evoluindo, ao longo dos tempos, porm continuava se
apresentando como uma relao mestre-escravo, em que o primeiro ditava a seqncia da interao.
Mesmo os sistemas ditos avanados apresentavam uma hierarquia rgida de menus e/ou formulrios,
que eram preenchidos pelo usurio medida em que este navegava por essa estrutura.

Interfaces grfico-interativas: o usurio como mestre


Com o avano tecnolgico, o custo/hora da CPU foi diminuindo, a ponto de se tornar cada vez mais
comum encontrar sistemas onde o usurio determinava como se dava a interao e a navegao por suas
estruturas.
Com o barateamento da tecnologia, os sistemas grficos foram se mostrando uma forma mais razovel
de representar estruturas complexas, onde a interao ocorre de forma assncrona.

O conceito de eventos como paradigma de programao


O funcionamento de um sistema grfico se d atravs de aes sobre os diversos elementos da
interface (objetos visuais). Cada uma destas aes corresponde um ou mais eventos enviados para esses
elementos. Estes eventos devem ser identificados e tratados de maneira adequada pelo programa
aplicativo.
Para manipular uma interface interativa, a programao tradicional se utiliza do recurso de um loop de
controle principal (case/switch), que gerencia as opes de navegao oferecidas pelo sistema.
A necessidade de cuidar de eventos assncronos com um paradigma sncrono acarreta uma relativa
complexidade deste loop principal.

O conceito de objeto (versus procedimento) como mecanismo de programao


O conceito de objeto consiste basicamente em agrupar dados e cdigo num mesmo elemento de
programao. Ao invs de procedimentos atuando sobre estruturas de dados, passamos a ter estes dados
(propriedades) associados a mtodos (procedimentos). Estes mtodos so os responsveis pelo controle de
fluxo e de informao entre os objetos do sistema.
Propriedades correspondem a campos de um registro (record/struct/class).
O conceito de objeto visual consiste na associao de uma representao grfica (elemento de
interface) a um trecho de cdigo responsvel pelo comportamento do objeto. A comunicao entre um
objeto de interface e a aplicao se d atravs da chamada de mtodos de outros objetos (de interface ou
no).

O pacote grfico orientado a eventos DOSGRAPH


DOSGRAPH uma biblioteca simples com apenas dez funes que possibilita uma programao
orientada a eventos. A funo principal a de captura de eventos, e s retorna quando ocorre um evento.
O eventos do DOSGRAPH so apenas os de mouse: click e unclick com os dois botes e mouse motion.

Uma aplicao tpica que utiliza o DOSGRAPH (e qualquer outro pacote orientado a eventos) tem
como ncleo um loop que captura os eventos e faz o processamento adequado. Todo o processamento
feito em resposta aos eventos.
Como aplicaes que utilizam o mouse so tipicamente grficas, o DOSGRAPH fornece vrias
funes de desenho que podem ser utilizadas, por exemplo dgLine, que desenha uma linha com a cor
corrente. As funes do DOSGRAPH podem ser classificadas em:
funes de desenho: dgLine, dgRectangle, dgFill, dgSetColor e dgSetMode;
funes de consulta: dgWidth e dgHeight;
funes de inicializao: dgOpen e dgClose;
funes de controle: dgGetEvent.
Os tipos e funes definidos pelo DOSGRAPH esto definidos no apndice A desta apostila.

Utilizando o DOSGRAPH em uma aplicao


Para fazer uma aplicao que use o DOSGRAPH, preciso incluir a biblioteca no projeto. O nome da
biblioteca dg.lib, e deve ser includa junto com os arquivos fonte que implementam a aplicao.
importante lembrar que a funo dgOpen deve ser chamada antes de qualquer outra funo do
DOSGRAPH. Os resultados das funes antes de dgOpen imprevisvel.
Para executar a aplicao DOSGRAPH, preciso que o arquivo egavga.lib esteja presente no diretrio
do executvel. Caso este arquivo no esteja presente, a funo dgOpen falha e gera uma mensagem de
erro.

Exerccio 11 - Uma aplicao orientada a eventos em C.


Implementar uma aplicao C que utilize o DOSGRAPH. Esta aplicao deve responder a um click
com o primeiro boto desenhando um retngulo na posio do cursor do mouse. Um click com o segundo
boto deve terminar a aplicao.

Exerccio 12 - Classe Application


Implementar uma classe que represente uma aplicao DOSGRAPH. Sempre que for necessrio fazer
uma nova aplicao, basta criar uma nova classe derivada que responda aos eventos tratados, sem
necessidade de usar as funes dgOpen, dgClose e dgGetEvent. Novas aplicaes s precisam se
preocupar com o processsamento dos eventos.

Captulo 8
Projeto de uma aplicao orientada a objetos
Exerccio 13 - Objetos grficos que se movem na tela.
Implementar uma aplicao grfica que permita drag de objetos. A organizao do programa deve ser
feita independentemente do tipo de objeto que ser arrastado. Podem coexistir objetos diferentes na tela.
Basicamente o funcionamento deve ser:
Assim que o programa comea, a tela est vazia;
Um click com o boto esquerdo no fundo da tela cria objetos novos e
Um click sobre um objeto comea o arraste.
Ateno para a ordem dos objetos na tela: se dois esto sobrepostos, um click na rea comum deve
pegar o objeto visualmente acima. Para diferenciar a ordem dos objetos na tela, por exemplo use cores
diferentes a cada criao.

Proposta para o exerccio 13


A biblioteca dosgraph deve ser usada para a implementao desta aplicao.
Pode-se imaginar que um sistema destes composto por uma rea de trabalho, uma lista para guardar
os objetos e os prprios objetos. Alm disso, se uma aplicao dosgraph representada por um objeto,
este tambm necessrio.
A figura abaixo exemplifica uma possvel implementao, com os objetos envolvidos e suas
interaes:

Aplicao

rea

Desenho1
Lista
Desenho2

Desenho N

A
estrutura
apresentada esta no nvel
de
projeto.
Para
a
aplicao, necessrio
definir a criao dos
objetos e os prprios
objetos em si, o que pode
ser feito especializando-se
algumas classes. Como a
funcionalidade do drag
independe do desenho, ela
deve estar em alguma das
classes acima. Coloque em
Desenho todos os mtodos
necessrios
para
esta
funcionalidade, para que a
Area possa manipul-los.

Captulo 9
Templates
Uma classe C++ normalmente projetada para armazenar algum tipo de dado. Muitas vezes a
funcionalidade de uma classe tambm faz sentido com outros tipos de dados. Este o caso de muitos
exemplos apresentados (por exemplo, Pilha).
Se uma classe vista simplesmente como um manipulador de dados, pode fazer sentido separar a
definio desta classe da definio dos tipos manipulados; isto , pode-se fazer uma descrio de uma classe
sem definir o tipo dos dados que ela manipula. Nesse caso, definio da classe parametrizada por um tipo
genrico T, sendo chamada C<T>. Esta construo no uma classe realmente, mas uma descrio do
conjunto de classes com o mesmo comportamento e que operam sobre um tipo T qualquer. Esta construo
denominada class template.
Com class templates, permitido ao programador criar um srie de classes distintas com a mesma
descrio, mas sobre tipos distintos. Por exemplo, pode-se construir uma pilha de inteiros (Pilha<int>) ou
pilha de strings (Pilha<char*>) apartir da mesma descrio. Esta descrio abstrata da template utilizada
pelo compilador para criar uma classe real em tempo de compilao, usando o tipo dos dados especificados
quando de seu uso.
Em alguns textos acadmicos, esta funcionalidade chamada de polimorfismo paramtrico.

Declarao de templates
Consideremos novamente a classe Pilha, que j apareceu com vrias implementaes. Em todos os
exemplos, a classe s armazenava inteiros, apesar de a funcionalidade nada ter a ver com o tipo de dado
envolvido. Neste caso, ao invs de reescrevermos uma nova classe pilha para cada novo tipo demandado,
pode-se definir uma template para a classe Pilha, onde o tipo armazenado um T qualquer. A forma deste
declarao mostrada abaixo:
template<class T> class Pilha {
struct elemPilha {
elemPilha* prox;
T val;
elemPilha(elemPilha*p, T v) { prox=p; val=v; }
};
elemPilha* topo;
public:
int vazia()
{ return topo == NULL }
void push(T v) { topo = new elemPilha(topo, v); }
T pop()
{ if (topo)
{
elemPilha *ep = topo;
T v = ep->val;
topo = ep->prox;
delete ep;
return v;
}
return -1;
}
};
Tendo em vista que uma template simplesmente uma descrio de uma classe, necessrio que toda esta
descrio tenha sido lida antes de alguma declarao que envolva esta template. Isto significa, em se tratando
de templates, que necessrio colocar no arquivo .h no apenas a declarao de classe, mas tambm a
implementao de seus mtodos.

Outra consequncia de templates serem apenas descries que erros semnticos s aparecem na hora de
usar a template. Durante a declarao da template apenas erros de sintaxe so checados. Mesmo que a
template em si tenha sido compilada sem erros, podem aparecer erros quando de sua utilizao.
Esta checagem semntica realizada todas as vezes que a template instanciada para algum tipo novo.
Isto porque na definio do cdigo da template no h nenhuma restrio quanto s operaes que podem ser
aplicadas ao tipo T. A checagem ento tem que ser feita para cada tipo.

Usando templates
Definida a implementao de nossa template de pilhas, pode-se utiliz-la para qualquer tipo T criando
pilhas de inteiros, strings,etc. Na criao de objetos destas classes pilhas, necessrio especificar o tipo de
pilha na declarao do objeto:
void main()
{
Pilha<int> intPilha;
// pilha de inteiros
Pilha<char*> stringPilha;
// pilha de strings
Pilha<Pilha<int>> intPilhaPilha; // pilha de pilha de inteiros
intPilha.push(10);
stringPilha.push( "teste" );
intPilhaPilha.push( intPilha );
}

Declarao de mtodos no inline


Quando da declarao de uma template, no obrigatoria a implementao de seus mtodos na forma
inline; isto , sua implementao pode no estar no corpo da declarao da classe. Para tal, basta especificar,
de maneira anloga a descrio de classes, a template a que o mtodo pertence.
Segue abaixo a implementao do mtodo pop fora do corpo da template:
template<class T> class Pilha {
// estrutura interna da template...
public:
int vazia()
{ return topo == NULL }
void push(T v) { topo = new elemPilha(topo, v); }
T pop();
// prottipo do mtodo pop
// implementado abaixo (fora da classe)
};
// Fim da declarao da classe
template<class T> T Pilha<T>::pop()
{ if (topo)
{ elemPilha *ep = topo;
T v = ep->val;
topo = ep->prox;
delete ep;
return v;
}
return -1;
}

Templates com vrios argumentos genricos


Templates no esto limitadas a terem apenas um nico argumento. Pode-se utilizar diversos argumentos
para parametrizar a descrio da classe. No exemplo abaixo, A Pilha armazena dois tipos de dados e ambos
so parmetros da template:

template<class T, class R> class Pilha {


struct elemPilha {
elemPilha* prox;
T t_val;
R r_val;
elemPilha(elemPilha*p, T t, R r)
{ prox=p; t_val=t; r_val=r; }
};
elemPilha* topo;
public:
int vazia()
{ return topo == NULL }
void push(T t, R r) { topo = new elemPilha(topo, t, r); }
void pop(T& t, R& r);
};

Templates com argumentos no genricos


Templates no esto limitadas a argumentos genricos; ou seja, tipos definidos pelo programador. Pode-se
utilizar como argumentos da template tipos primitivos da linguagem como inteiros ou characteres. No
exemplo abaixo, tem-se uma pilha que armazena um nmero fixo de elementos de um mesmo tipo:
template<class T, int S> class Pilha {
struct elemPilha {
elemPilha* prox;
T t_val[S];
elemPilha(elemPilha*p, T* t);
};
elemPilha* topo;
public:
int vazia();
void push(T* t);
void pop(T* t);
};

Templates de funes
Templates tambm podem ser usadas para definir funes. A mesma motivao para classes vale neste
caso. Algumas vezes funes realizam operaes sobre dados sem utilizar o contedo deles, ou seja,
indepentemente de que tipo de dado seja.
Suponha que precisamos testar a magnitude de dois elementos quaisquer. A seguinte macro resolve este
problema:
#define max(a,b) ((x>y) ? x : y)
Por muito tempo macros como esta foram usadas em programas C, mas isto tem os seus problemas. A
macro funciona, mas impede que o compilador teste os tipos dos elementos envolvidos. A macro poderia ser
utilizada para comparar um inteiro com um ponteiro, sem que sejam gerados erros.
Poderamos usar uma funo como esta:
int max(int a, int b)
{
return a > b ? a : b;
}
Mas esta funo s funciona para inteiros. Se o nosso programa s trabalha com escalares, poderamos
escrever uma funo que trabalhe com double:
double max(double a, double b)
{

return a > b ? a : b;
}
Nesse caso, o compilador se encarrega de converter os tipos char, int etc. para double, e a funo
funcionaria para todos estes casos.
Existem pelo menos duas limitaes nesta verso. Uma delas diz respeito ao tipo dos parmetros: apenas
tipos que podem ser convertidos para double podem usar esta funo. Isto significa que objetos e ponteiros
no podem ser utilizados. A outra limitao se refere ao tipo de retorno: este sempre double, independente
do tipo passado. Suponha que a funo display seja sobrecarregada para imprimir vrios tipos de dados.
Agora considere o cdigo:
display(max('1', '9'));
Apesar de estarmos trabalhando com caracteres, a funo display a ser chamada ser a verso que trabalha
com double, e o resultado ser 57.00, que o cdigo ASCII do caractere 9.
A opo de sobrecarregar max para vrios tipos tambm no boa, pois teramos que reescrever o cdigo,
que seria idntico, para todos os tipos que quisssemos usar. Ainda assim o problema no estaria resolvido,
pois novos tipos no poderiam ser usados sem que se criasse outra verso sobrecarregada de max.
Na verdade, qualquer tipo de dado que possua operaes de comparao pode ser usado com max, e
sempre da mesma maneira. No possvel escrever uma nica funo que trate todos os tipos, mas o
mecanismo de templates possibilita descrever, para o compilador, como estas funes podem ser
implementadas:
template<class T> T max( T a, T b )
{
return a > b ? a : b;
}
Esta funo faz sentido para qualquer tipo T. Na realidade existir uma implementao para cada tipo que
for usado com esta funo dentro do programa, mas estas funes sero geradas transparentemente pelo
compilador. O uso de templates de funes no exige que se especifique explicitamente os tipos genricos na
chamada, como em templates de classes. Basta usar como se existisse uma funo especfica para o tipo
envolvido:
void main()
{
printf("%c", max('1', '9'));
}
Repare que, apesar de a funo ser usada para comparar caracteres, em nenhum momento aparece a
palavra char. O compilador sabe qual max precisa ser chamada pelo tipo dos parmetros usados. Por causa
disto, os tipos genricos de templates de funes devem sempre aparecer nos parmetros da funo. Caso isto
no acontea, ser sinalizado um erro de sintaxe na linha da declarao da template.
Assim como em templates de classes, templates de funes podem ter vrios parmetros genricos.

Exerccio 14 - Classe Vector para qualquer tipo.


Implementar com templates uma classe Vector com a mesma funcionalidade da desenvolvida na seo de
sobrecarga de operadores, mas que possa ser usada com qualquer tipo.

Tratamento de Excees
Quando do desenvolvimento de bibliotecas, possvel escrever cdigo capaz de detectar erros de
execuo mas, em geral, no possvel fazer seu tratamento. Por outro lado, o usurio de uma biblioteca
capaz de fazer o correto tratamento de uma exceo mas no capaz de detect-la.
O conceito de exceo introduzido para ajudar neste tipo de problema. A idia fundamental que uma
funo que detecte um problema e no seja capaz de resolv-lo acuse a exceo esperando que quem a
chamou seja capaz de realizar o tratamento adequado.

Funcionamento bsico
O funcionamento dos tratadores de exceo composto de diversas etapas:
Definio das excees que uma classe pode levantar;
Definio de quando uma classe acusa uma exceo;
Definio do(s) tratador(es) das excees.
A definio da execees que podem ser levantadas feita na construo da classe. Neste momento
define-se as condies de erro e os representa sob a forma de uma classe. Desta forma, cada condio de
exceo descrita por uma classe de exceo. Por exemplo, considere como representar e tratar o erro de
indexao fora dos limites de um array dada pela classe Vector:
class Vector {
int* p;
int sz;
public:
class Range{ }; // classe de tratamento de exceo que
// representa acesso com ndice invlido
int& operator[]( int i );
};
A classe Range, a princpio sem dados internos, representa a condio de erro para acesso com ndice no
vlido (menor que zero, maior que o espao alocado, etc.)
A definio dos momentos da exceo so tambm definidos na construo da classe. A acusao das
excees feita normalmente nos mtodos da classe que encontrem alguma situao de erro. O levantamento
de uma exceo feita pelo comando throw que, conforme no nosso exemplo, acionado quando o ndice do
array invlido. Nestas situaes, os objetos da classe Range so utilizados como excees e so acusados da
seguinte forma:
int& Vector::operator[]( int i )
{
if ( i<0 || i>size-1 ) throw Range();
// Range() cria um objeto da classe Range
//
//
//
//

Quando indexamos o vetor com limites invlidos


acusada a exceo correspondente
se a funo que chamou operator[] souber trat-la,
teremos o tratamento adequado.

}
A definio dos tratadores aparecem, normalmente, nas funes que utilizam servios das classes que
levantam excees. No caso de C++, estas funes podem selecionar quais as excees que sero tratadas e
trechos onde estas podem surgir.
No nosso exemplo, a funo que precisa detectar a utilizao de ndices fora do limite indica seu interesse
pelo tratamento colocando cdigo correspondente na seguinte forma:
void f( Vector& v )
{
//.. cdigo qualquer sem tratamento
try{
//.. cdigo qualquer com tratamento
operao_qualquer( v );
}
catch( Vector::Range ) {
// Aqui se encerra o cdigo de tratamento da exceo
// Range.
// A funo operao_qualquer apresentou a exceo
// que est sendo tratada.

// Esse cdigo somente ser executado se e somente se


// a operao_qualquer fizer uso de indexao invlida.
}
//.. cdigo qualquer sem tratamento
}
A construo
catch( /* nome da exceo */ ) { /* cdigo */ }
denominada manipulador de exceo (exception handler). Esta construo somente pode ser utilizada
depois de um bloco prefixado com a palavra reservada try ou aps outra construo catch. Os parnteses
encerram a declarao dos tipos de objetos aonde o manipulador pode executar. Se a funo
operao_qualquer ou qualquer outra funo chamada por operao_qualquer causar uma indexao
invlida no array, ser gerada uma exceo que ser pega pelo manipulador e seu cdigo executado.
Se um manipulador pegou uma determinada exceo, esta foi devidamente tratada e qualquer outro
manipulador ainda existente se torna irrelevante. Em outras palavras, apenas o manipulador mais
recentemente encontrado pelo controle da linguagem ser executado. Por exemplo, dado que a funo f pega
uma exceo Vector::Range, uma funo que chame f jamais pegar a exceo Vector::Range.
int ff( Vector& v )
{
try{
f(v);
}
catch( Vector::Range )
{ // este cdigo jamais ser executado ...
}
}
Naturalmente, um programa capaz de tratar diversas excees. Esses erros so mapeados com nomes
distintos. Continuando o exemplo de Vector, trataremos mais um caso: criao de array com tamanho fora dos
limites. Temos:
class Vector {
int* p;
int size;
int max 512; // nmero mximo de elementos
public:
class Range{ }; //classe de tratamento de exceo
class Size{ }; //classe de tratamento de exceo
int& operator[]( int i );
Vector( int sz )
{
if ( sz < 0 || max < sz ) throw Size();
// continuao do construtor...
}
};
O usurio da classe Vector pode discriminar a exceo pondo diversos manipuladores dentro do bloco
precedido por try:
void f( Vector& v )
{
try {
qualquer_operao(v);
}
catch( Vector::Range ) {
// cdigo de tratamento de indexao invlida

}
catch( Vector::Size ) {
// cdigo de tratamento para criao de vetor muito grande
}
// esse cdigo executado se no tiver ocorrido nenhuma
// exceo.
}

Nomeao de excees
Uma exceo tomada pelo manipulador no pelo seu tipo mas sim por um objeto. Havendo necessidade
de transmitir alguma informao do levantamento da exceo para o manipulador, necessrio haver algum
mecanismo de colocar tal informao neste objeto. Isto feito colocando-se campos dentro da classe que
representa a exceo e tomando seus valores nos manipuladores. Para tomarmos estes valores nos handles,
necessria a definio de um nome para o objeto criado na acusao.
No exemplo criado, importante saber qual o valor que foi usado como ndice na exceo Vector::Range
(indexao fora dos limites):
class Vector{
// ...
public:
class Range {
public:
int index;
// criao do campo que diz o valor invlido
Range( int i ) { index = i; }
// a criao do objeto indica o valor invlido
};
int& operator[]( int i )
// ...
};
int& Vector::operator[]( int i )
{
if ( i<0 || i>size-1 ) throw Range(i);
// acusa-se a exceo indicando
// o valor ndice invalido ao construir Range
return p[i];
}
Para examinar o ndice incorreto, o manipulador deve dar um nome ao objeto da exceo:
void f( Vector& v )
{ /...
try {
qualquer_operao(v);
}
catch( Vector::Range r ) {
// 'r' o nome do objeto Range acusado no operador []
printf( "ndice errado: %d \n", r.index );
exit(0);
}
// ...
};
interessante notar que no caso de templates, tem-se a opo de nomear a exceo de modo que cada
classe instanciada pela template tenha sua prpria classe de exceo:

template<class T> class Allocator {


// ...
class Exhausted {}; // classe do tratamento de exceo
// ...
};
void f (Allocator<int>& ai, Allocator<double>& ad )
{
try {
// ...
}
catch (Allocator<int>::Exhausted) {
// ...
// tratamento de inteiros
}
catch (Allocator<double>::Exhausted) {
// ...
// tratamento de doubles
}
}
Alternativamente, uma exceo pode ser comum a todas as classes instanciadas pela template:
class Allocator_Exhausted {};
template<class T> class Allocator {
// ...
};
void f( Allocator<int>& ai, Allocator<double>& ad )
{
try {
// ...
}
catch( Allocator_Exausted ) {
// ...
// tratamento para ambos os tipos
}
}

Agrupamento de excees
Normalmente as excees podem ser categorizadas em famlias. Por exemplo, pode-se imaginar um erro
matemtico que inclua as excees de overflow, underflow, diviso por zero, etc. A exceo de erro
matemtico (MATHERR) pode ser determinada pelo conjunto de erros que podem ser produzidos em uma
biblioteca de funes numricas.
Uma maneira de fazer MATHERR determin-la como um tipo de todos os possveis erros numricos:
enum MATHERR { Overflow, Underflow, ZeroDivide };
e na funo que trata os erros:
void f( .... )
{
try {
// ...
}
catch( MATHERR m ) {
switch( m ) {

case
//
case
//
case
//
}

Overflow:
...
Underflow:
...
ZeroDivide:
...

}
}
De outra maneira, C++ usa a capacidade de herana e de funes virtuais para evitar este tipo de switch.
possvel a utilizao de herana para descrever colees de excees. Por exemplo:
class MATHERR {};
class Overflow: public MATHERR {} ;
class Underflow: public MATHERR {} ;
class ZeroDivide: public MATHERR {} ;
// ....
Para este caso, existem muitas ocasies em que deseja-se fazer o tratamento de MATHERR sem saber
precisamente de que tipo o erro. Com a utilizao de herana, possvel dizer:
try {
// ...
}
catch( Overflow )
// tratamento de
}
catch ( MATHERR )
// tratamento de
}

{
overflow ou tudo derivado de overflow
{
qualquer outro erro numrico

A organizao de excees em hierarquias pode ser importante para a robustez do cdigo de um


programa. Consideremos o tratamento de todas as excees de nossa biblioteca numrica sem o agrupamento
destas. Neste caso, as funes que utilizam esta biblioteca teriam que exaustivamente determinar e tratar toda
a lista de erros.
try {
// ...
}
catch (Overflow) { /* ..... */ }
catch (Underflow) { /* ..... */ }
catch (ZeroDivide) { /* ..... */ }
// e todas as outras excees!!!
Isto no somente tedioso mas d margem ao esquecimento de alguma exceo. Alm, uma determinada
funo que desejar fazer o tratamento de qualquer erro numrico (sem saber que tipo de erro) precisa ser
constantemente atualizada quando do aparecimento de novas excees. Por exemplo: o logaritmo de nmero
menor ou igual a zero; o que implica tambm em recompilao destas funes.
Neste sentido muito mais prtico fazer:
try {
// ...
}
catch (MATHERR) { /* ..... */ }
// trata qualquer erro matemtico.
que garante sempre o tratamento sem a necessidade de manuteno do cdigo quando da introduo de
novas excees.

Excees derivadas
A utilizao de hierarquias de excees naturalmente direciona os manipuladores que esto interessados
somente em um subconjunto da informao carregada pelas excees. Em outras palavras, uma exceo
normalmente tomada por um manipulador da classe bsica ao invs de um da classe exata. Por exemplo:
class MATHERR {
// ...
virtual void debug_print() {};
};
class int_overflow : public MATHERR {
public:
char op;
int opr1, opr2;
int_overflow( const char p, int a, int b )
{ op=p; opr1=a; opr2=b; }
virtual void debug_print() // redefinio de debug_print
{ printf( "operador:%c:( %d, %d )", op, opr1, opr2 ); }
};
void f()
{
try{
g();
}
catch( MATHERR m ) { /* ... */ }
}
Quando um manipulador MATHERR encontrado, m um objeto MATHERR mesmo que a chamada de g
tenha acusado um int_overflow. Isto implica que a informao extra encontrada em int_overflow est
inacessvel; isto , se dentro do tratador chamarmos a funo debug_print, no conseguiremos ver nada sobre
o erro de overflow de inteiros. Isto devido ao fato do compilador no fazer late-binding com o objeto.
No entanto, ponteiros e referncias podem ser utilizados para evitar esta perda de informao. Para tal,
pode-se escrever:
int add( int x, int y )
{
if ( x>0 && y>0 && x>MAXINT - y
|| x<0 && y<0 && x<MININT + y )
throw int_overflow( '+', x, y );
return x + y;
}
void f()
{
try {
add( 1, 2 ); // ok
add( MAXINT, 3 ); // causa exceo
}
catch( MATHERR& m ) { // recebe no manipulador uma referncia
// ...
m.debug_print();
// chama o mtodo de int_overflow!!!
}
}

Re-throw
Dada uma funo que capture uma exceo, no incomum para um manipulador chegar a concluso que
nada pode ser feito a respeito do erro. Neste caso, a coisa tpica a ser feita o acusao da exceo novamente
(re-throw), esperando que outro manipulador possa faz-lo melhor. Por exemplo:
void h()
{
try {
// ...
}
catch( MATHERR ) {
if ( posso_tratar() ) tratamento();
else throw; // re-throw
}
}
Um re-throw indicado pelo comando throw sem argumentos. A exceo de relevantamento a exceo
original tomada e no somente a parte que era acessvel como MATHERR. Em outras palavras, se um
int_overflow foi acusado, a funo que chamou h pode ainda tomar um int_overflow que h tomou como
MATHERR e decidiu reacusar.
void k()
{
try {
h();
// ...
}
catch( int_overflow ) {
// ...
}
}
A verso abaixo deste tipo de comportamento pode ser til. Assim como em funes, pode-se utilizar ...
(indicando qualquer argumento) de modo que catch(...) signifique qualquer exceo. Por exemplo:
void m()
{
try {
// ...
}
catch(...) {
limpeza();
throw;
}
}
Isto , se qualquer exceo ocorrer, resultado da execuo de parte de m(), a funo limpeza() ser
chamada no manipulador e a exceo que causou a chamada da funo limpeza() ser reacusada.
Devido ao fato de que excees derivadas podem ser tratadas por manipuladores para mais de um tipo de
exceo, a ordem em que os estes aparecem aps o bloco de try relevante. Os tratadores so escolhidos em
ordem. Por exemplo:
try {
// ...
}
catch( ibuf ) {
// tratador de input overflow
}
catch( io ) {
// tratador de qualquer erro de I/O

}
catch( stdlib ) {
// tratador de qualquer erro em bibliotecas
}
catch( ... ) {
// tratador de qualquer outra exceo
}

Especificao de excees
A acusao e o tratamento de excees afetam o relacionamento entre as funes. Neste sentido,
interessante haver um mecanismo de especificar quais excees que podem ser levantadas como parte da
declarao de uma funo. Por exemplo:
void f( int a ) throw (x2, x3, x4);
especifica que a funo f s pode acusar as excees x2, x3, x4 e suas derivadas, nada mais. Deste modo,
est garantindo para quem a chama que durante sua execuo nenhuma outra exceo ser levantada. Se, por
acaso, algo acontecer que invalide esta garantia, a tentativa de acusao de uma exceo indevida ser
transformada em uma chamada para a funo unexpected. O significado default para unexpected a chamada
a terminate, que normalmente representa um abort.
Desta forma, escrever:
void f( int a ) throw (x2, x3, x4)
{ /* implementacao qualquer */ }
significa a mesma coisa que:
void f( int a )
{
try{
/* implementao qualquer */
}
catch(x2) { throw; } // re-throw
catch(x3) { throw; } // re-throw
catch(x4) { throw; } // re-throw
catch(...){ unexpected(); }
}
Mais que economia de digitao, o uso de especificao de excees explicita as excees que podem
surgir na definio da funo (.h) o que nem sempre aconteceria se esta definio ficasse em sua
implementao.
Uma funo sem especificao pode levantar qualquer exceo.
int f ();
enquanto que uma funo sem a possibilidade de acusar qualquer exceo declarada com uma lista
explicitamente vazia:
int g() throw();

Excees indesejadas
O mal uso de especificaes de excees pode levar a chamadas a funo unexpected, que indesejvel a
no ser no caso de testes. Pode-se evitar isto por uma boa estruturao e organizao das excees ou pela
interceptao das chamadas a unexpected.
A funo set_unexpected serve para interceptarmos estes casos. Esta funo redefine o comportamento do
sistema quando de uma exceo indesejada retornando o tratador antigo.

Abaixo temos um exemplo deste mecanismo. Neste caso, cria-se uma classe que representa um trecho
onde excees no previstas devem ser tratadas.
typedef void(*functype)();
functype set_unexpected( functype );
class MyPart {
functype old;
public:
MyPart( functype f ) { old = set_unexpected( f ); }
~MyPart() { set_unexpected( old ); }
}
void new_trat() { printf("novo tratamento.\n"); }
void f()
{
MyPart( &new_trat ); // construtor implica em redefinio
g();
} // destrutor reseta unexpected() anterior
Neste caso, a execuo de f protegida contra erros de excees no desejadas.

Excees no tratadas
Uma exceo acusada e no tratada implica na chamada da funo terminate. Esta tambm chamada se o
mecanismo de excees de C++ encontrar a pilha corrompida. terminate executa a ltima funo recebida
como argumento da funo set_terminate.
Este mecanismo serve como mais um nvel para erros de exceo. normalmente utilizado para medidas
mais drsticas no sistema como: aborto da execuo do processo, reinicializao do sistema, etc.
A redefinio deste comportamento feito de modo anlogo ao unexpected.
typedef void (*PFV) ();
PFV set_terminate (PFV);

Exemplo: calculadora RPN com tratamento de excees


Como a calculadora usa a classe Stack, esta ser atualizada para gerar excees em caso de erro. Os erros
possveis so push com pilha cheia ou pop com pilha vazia:
// Excecoes da pilha
class StackError{};
class StackFull : public StackError {
public:
int size;
StackFull( int s ) { size = s; }
};
class StackEmpty : public StackError {};
class Stack {
friend class StackIterator;
int size;
int top;
int *elems;

public:
Stack(int s = 50) { size = s; top = 0;
elems = new int[size]; }
~Stack()
{ delete [] elems; }
void push(int i)
{ if (top>=size) throw StackFull(size);
elems[top++]=i; }
int pop()
{ if (top==0) throw StackEmpty();
return elems[--top]; }
int empty()
{ return top == 0; }
};
A prpria calculadora tem seus erros:
class RpnError{};
class RpnNoOperands : public RpnError, public StackEmpty {};
class RPN : public Stack {
void getop(int* n1, int*
public:
void sum() { int n1, n2;
void sub() { int n1, n2;
void mul() { int n1, n2;
void div() { int n1, n2;
};

n2)

throw(RpnNoOperands);

getop(&n1,
getop(&n1,
getop(&n1,
getop(&n1,

&n2);
&n2);
&n2);
&n2);

push(n1+n2);
push(n1-n2);
push(n1*n2);
push(n1/n2);

void RPN::getop(int* n1, int* n2) throw(RpnNoOperands)


{
try {
*n2 = pop();
*n1 = pop();
}
catch ( StackEmpty )
{ throw RpnNoOperands(); }
}

Exerccio 15 - Template Array com tratamento de excees


Introduzir tratamento de excees na template Array.

}
}
}
}

Vous aimerez peut-être aussi