Tópicos em C++ Claudio Esperança Paulo Roma Cavalcanti 2002 LCG/UFRJ. All rights reserved. 1 Classes e Objetos • C++ é mais do que C com uns poucos sinos e apitos adicionais. • O mecanismo básico para atingir programação orientada a objeto em C++ é o conceito de classe. ▪ Fornece formas de encapsulamento e proteção de informação. ▪ Permite reutilização de código. 2002 LCG/UFRJ. All rights reserved. 2 O que é OO? • Programação orientada a objeto foi o paradigma dominante dos anos 90. • Criou o conceito de objeto, que é um tipo de dado com uma estrutura e um estado. ▪ Cada objeto define um conjunto de operações que podem acessar ou manipular esse estado. ▪ Tipos definidos pelo usuário devem se comportar da mesma maneira de tipos prédefinidos (fornecidos pelo compilador). 2002 LCG/UFRJ. All rights reserved. 3 Tipos de Dados • Um tipo básico de dados de uma linguagem, como inteiro, real, ou caractere, fornece certas coisas: ▪ Podem-se declarar novos objetos, com ou sem iniciação. ▪ Pode-se copiar ou testar quanto à igualdade. ▪ Pode-se executar a entrada e a saída de dados com esses objetos. 2002 LCG/UFRJ. All rights reserved. 4 Objetos • Um objeto é uma unidade atômica. ▪ Não pode ser dissecado por um programador. • Proteção de informação torna os detalhes de implementação inacessíveis. • Encapsulamento é o agrupamento de dados, e operações que se aplicam a eles, para formar um agregado. ▪ Mas escondendo a os detalhes de implementação. • Uma classe é o mesmo que uma estrutura, mas com os membros protegidos por default. 2002 LCG/UFRJ. All rights reserved. 5 Encapsulamento • O princípio de atomicidade é conhecido por encapsulamento. ▪ O usuário não tem acesso direto às partes de um objeto ou a sua implementação. ▪ O acesso é feito indiretamente, através de funções fornecidas com o objeto. • É uma forma de imitar a vida real. ▪ Aparelhos eletrônicos possuem o seguinte aviso: “Não abra – não há partes consertáveis por um usuário”. ▪ Pessoas sem treinamento que tentam consertar equipamentos com esses avisos acabam quebrando mais do que consertando. 2002 LCG/UFRJ. All rights reserved. 6 Reutilização de Código • A idéia é usar os mesmos componentes sempre que possível. • Quando o mesmo objeto é a solução fica fácil. ▪ O difícil é quando se necessita de um objeto ligeiramente diferente. • C++ fornece diversos mecanismos para isso. ▪ Se a implementação for idêntica exceto pelo tipo básico do objeto, pode-se usar uma template. ▪ Herança é o mecanismo que permite estender a funcionalidade de um objeto. 2002 LCG/UFRJ. All rights reserved. 7 Classes • Classes são usadas para encapsular informação. • Já que as funções que manipulam o estado do objeto são membros da classe, elas são acessadas pelo operador “.”, como em uma estrutura de C. • Quando se chama uma função de uma classe, na realidade se está passando uma mensagem para o objeto. • A diferença básica entre OO de C++ e o velho C é puramente filosófica: em C++ o objeto tem o papel principal. 2002 LCG/UFRJ. All rights reserved. 8 Para Não Esquecer Jamais • Se você deseja usar OO, acostume-se a esconder todos os dados das classes. ▪ C++ não é uma linguagem para preguiçosos. • Para isto existe uma seção de dados privados. ▪ O compilador se esforçará ao máximo para mantê-los inacessíveis ao “mundo exterior”. • Toda classe possui um construtor e um destrutor, que dizem como uma instância do objeto deve ser iniciada e destruída. ▪ Procure escrevê-los sempre. Evite defaults. 2002 LCG/UFRJ. All rights reserved. 9 10 Construtor class memoryCell { public: memoryCell ( ) { value = 0; } int read ( ) const { return value; } void write ( int x ) { value = x; } Exemplo Bobo private: int value; }; value está protegido 2002 LCG/UFRJ. All rights reserved. main ( ) { memoryCell m; m.write ( 5 ); cout << “Conteúdo da #if 1 << m.read ( ) #else << m.value #endif << ‘\n’; return 0; Instancia a classe Armazena célula é: “ um valor Maneira correta Erro!!! value é privado!!! 11 Uso da Classe } Diretiva de préprocessamento 2002 LCG/UFRJ. All rights reserved. Interface • A interface descreve o que pode ser feito com o objeto. ▪ Necessita de uma boa convenção de nomes. ▪ Exemplo: primeira letra de nome de classe ou função minúscula, demais palavras do nome com primeira letra maiúscula (sem “_”). ▪ memoryCell. ▪ Dados: ponteiros com prefixo ptr e pode usar “_”. ▪ Constantes e tipos enumeráveis em caixa alta: ◦ GL_FLOAT, GLUT_DOUBLE; ▪ Prefixo com o nome do pacote. ◦ glLoadIdentity(), glVertex3f. 2002 LCG/UFRJ. All rights reserved. 12 Documentação • É fundamental uma documentação mínima em todo o arquivo de um sistema. • Existem ferramentas muito boas de domínio público, como o Doxygen. ▪ Basta colocar diretivas na forma de comentários na própria interface. ▪ É melhor acrescentar as diretivas durante a confecção da interface para não ter de voltar depois de “má vontade”. 2002 LCG/UFRJ. All rights reserved. 13 // memoryCell.h 14 // evita incluir a interface de novo se ela já foi // incluída por outro arquivo. #ifndef __MEMORY_CELL__ #define __MEMORY_CELL__ /** * A very simple interface for a generic class. * Doxygen will generate the documentation, * in html, rtf and/or latex automatically. */ class memoryCell { public: /** empty construtor. * Sets the content of this cell to value. * * @param value new value for this cell. */ memoryCell ( int value = 0 ); Interface memoryCell /// destrutor. Não faz nada. ~memoryCell ( ) { } 2002 LCG/UFRJ. All rights reserved. 15 /** the correct way of returning the content of this cell. * @return the content of this cell. */ Não altera int read ( ) const; dados desta classe Altera dados desta cell. classe /** sets a new value for this cell. * @param x new value. */ void write ( int x ); private: /// holds the content of this int cell_value; }; #endif Interface memoryCell 2002 LCG/UFRJ. All rights reserved. Implementação • A implementação contém os detalhes de como a interface foi codificada, de forma a atender as especificações do projeto. • As declarações dos nomes das funções ficam na declaração da classe e as implementações são definidas depois, normalmente num arquivo separado (.C, .cpp, ou .cc). ▪ Usam a sintaxe de função mais o nome da classe e o operador de escopo “::”. 2002 LCG/UFRJ. All rights reserved. 16 // memoryCell.C #include memoryCell.h 17 Construtor memoryCell::memoryCell ( int value ) { this->cell_value = value; Membro } int memoryCell::read ( ) const { desta classe return this->cell_value; getter } void memoryCell::write ( int x ) { this->cell_value = x; setter } Implementação memoryCell 2002 LCG/UFRJ. All rights reserved. Construtores e Destrutores • O construtor diz como o objeto é declarado e iniciado. ▪ Se a iniciação não casar com nenhum construtor, o compilador reclamará. • O destrutor diz como o objeto será destruído quando sair de escopo. ▪ No mínimo deve liberar a memória que foi alocada por chamadas “new”no construtor. ▪ Se nenhum destrutor for declarado será gerado um default, que aplicará o destrutor correspondente a cada dado da classe. 2002 LCG/UFRJ. All rights reserved. 18 Construtor de Cópia • O construtor de cópia é chamado sempre que o objeto for passado ou retornado por valor. • Se nenhum for declarado será criado um default, que aplicará o construtor de cópia correspondente a cada dado da classe. • Se a declaração for privada, o construtor de cópia será desativado (não poderá ser invocado). • Ou escreva um construtor de cópia decente ou então desative-o. 2002 LCG/UFRJ. All rights reserved. 19 20 memoryCell ( const memoryCell& m ) { *this = m; Operador de } atribuição Exemplo de Construtor de Cópia Construtor de Cópia Uso: memoryCell m1 ( 4 ); memoryCell m2 ( m1 ); 2002 LCG/UFRJ. All rights reserved. Operador de Atribuição • É usado para copiar objetos do mesmo tipo. ▪ O operador de cópia cria um novo objeto, mas o operador “=“ age sobre um objeto já existente. • Operadores de atribuição retornam, em geral, referências constantes. Retorno por valor não é uma boa idéia ... • Retornar uma referência não constante torna (A = B) = C válido, mas sem sentido. ▪ O resultado é atribuir C à referência A, sem nunca ter atribuído à B. • Se não for definido haverá uma cópia membro a membro dos dados da classe por default. 2002 LCG/UFRJ. All rights reserved. 21 22 const memoryCell& memoryCell::operator = ( const memoryCell& m ) { this->cell_value = m.cell_value; return *this; Referência } para este objeto Exemplo de Operador = Operador de atribuição Uso: memoryCell m1 ( 4 ); memoryCell m2 = m1; 2002 LCG/UFRJ. All rights reserved. Iniciação X Construtor memoryCell ( int value = 0 ) : cell_value ( value ) { } • A seqüência depois de “:” é a lista de iniciação. • Os membros são iniciados na ordem em que são declarados e não na ordem da lista. • O ideal é iniciar cada membro da classe pelo seu próprio construtor. • É preferível iniciar os membros da classe usando listas de iniciação ao invés de atribuir no construtor. ▪ Cada membro não especificado na lista é iniciado pelo seu construtor vazio. Só depois é que as atribuições são feitas (trabalho dobrado). ▪ Se a iniciação não for simples, só aí usa-se o corpo do construtor. 2002 LCG/UFRJ. All rights reserved. 23 Sobreposição de Operadores • Em C++ todos os operadores podem ser sobrepostos, a exceção de: “.”, “.*”, “?” e “sizeof”. ▪ Precedência e aridade não são alteradas. • Um operador binário retorna, em geral, um objeto por valor, porque o resultado é armazenado em um temporário. ▪ Também pode ser implementado invocando o operador de atribuição. 2002 LCG/UFRJ. All rights reserved. 24 25 const memoryCell& memoryCell::operator += ( const memoryCell& m ) { this->cell_value += m.cell_value; return *this; Referência } para este objeto Exemplo de Sobreposição memoryCell memoryCell::operator + ( const memoryCell& m ) const { memoryCell temp ( *this ); Adiciona o temp += m; segundo return temp; Retorna operando } temp por valor 2002 LCG/UFRJ. All rights reserved. Templates • Templates servem para escrever rotinas que funcionam para tipos arbitrários. • O mecanismo baseado em typedef permite criar rotinas genéricas. ▪ Mas não é suficiente se queremos rotinas que funcionem com dois tipos diferentes. • Uma template não é uma função comum, mas sim um padrão para criar funções. • Quando uma template é instanciada com um tipo particular, uma nova função é criada. • A template é expandida (como uma macro) para prover uma função real. ▪ O compilador gera código a partir da template para cada combinação diferente de parâmetros. 2002 LCG/UFRJ. All rights reserved. 26 Exemplo de Função Template template <class Etype> inline const Etype& Max ( const Etype& a, const Etype& b ) { return a > b ? a : b; } • • • • Etype define o tipo do parâmetro da template. O código da template pode ser substituído como uma macro, caso se use a opção inline. Deve-se retornar uma referência, pois não se sabe de antemão o tamanho do dado retornado. O operador “>”deve estar definido no tipo Etype. 2002 LCG/UFRJ. All rights reserved. 27 Classes Template • A sintaxe é similar ao de uma função template. • Não há sentido em gerar uma biblioteca de templates. ▪ Lembre-se que o compilador não sabe que tipos serão necessários. Logo, não gera código algum. ▪ Ou todo o código da template está junto com a interface, ou a template deve ser instanciada de antemão para cada tipo a ser usado na aplicação. 2002 LCG/UFRJ. All rights reserved. 28 // memoryCell.h #ifndef __MEMORY_CELL__ #define __MEMORY_CELL__ /** * A very simple interface for a template class. */ template <class Etype> class memoryCell { public: /** empty construtor. * Sets the content of this cell to value. */ memoryCell ( const Etype& value = Etype() ) { cell_value = value; } Especificação template antes da classe 29 Template memoryCell /** the correct way of returning the content of this cell. * @return the content of this cell. */ const Etype& read ( ) const { return cell_value; } Referência this cell. constante /** sets a new value for * @param x new value. */ void write ( const Etype& x ) { cell_value = x; } private: /// holds the content of this cell. Etype cell_value; }; #endif Pode assumir qualquer tipo 2002 LCG/UFRJ. All rights reserved. main ( ) { memoryCell <int> m; m.write cout << #if 1 << #else << #endif << 30 Instancia a template com int ( 5 ); “Conteúdo da célula é: “ m.read ( ) Uso da Template memoryCell m.cell_value ‘\n’; return 0; } 2002 LCG/UFRJ. All rights reserved. Herança • Talvez o principal objetivo de programação orientada a objeto seja a reutilização de código. • Templates são apropriadas quando a funcionalidade básica do código é independente de tipo. • Herança serve para estender a funcionalidade de um objeto. ▪ Criam-se novos tipos com propriedades restritas ou estendidas do tipo original. 2002 LCG/UFRJ. All rights reserved. 31 Polimorfismo • Polimorfismo permite que um objeto possa armazenar vários tipos diferentes de objetos. • Quando uma operação for aplicada a um objeto polimorfo será selecionada automaticamente aquela adequada ao tipo armazenado. • Sobreposição (overload) de funções e operadores é um exemplo de polimorfismo. ▪ Neste caso, a seleção da função é feita em tempo de compilação, o que limita o comportamento polimorfo. 2002 LCG/UFRJ. All rights reserved. 32 Reutilização • Em C++ é possível postergar a seleção da função até que o programa esteja sendo executado. ▪ O mecanismo básico usa herança. • Freqüentemente, no projeto de uma nova classe, descobre-se que há uma classe similar escrita anteriormente. ▪ Reutilização de código sugere que não se comece do zero, mas que se escreva uma nova classe baseada na classe existente. • Em C++ o mecanismo é criar uma classe abstrata polimorfa que possa armazenar os objetos das classes similares. 2002 LCG/UFRJ. All rights reserved. 33 Exemplos • Classe Vetor: Vetores limitados. • Classe Data: Calendários diversos (Gregoriano, Hebreu, Chinês). • Impostos: vários tipos de contribuintes (solteiro, casado, separado, cabeça de casal). • Formas: (círculos, quadrados, triângulos). 2002 LCG/UFRJ. All rights reserved. 34 Três Opções • Definir classes completamente independentes. ▪ Implica em escrever vários pedaços de código idênticos. ▪ Aumenta a chance de erro e cada mudança deve ser replicada. • Usar uma única classe e controlar as funções por if / else e switches. ▪ Manutenção é difícil, pois cada alteração requer recompilar todo o código. ▪ Extensão das classes por um programador não é possível a menos que todo o fonte esteja disponível. ▪ If / else gasta tempo de execução, mesmo que o resultado seja conhecido em tempo de compilação. ▪ Não há segurança de tipos, pois se toda forma está contida em uma única classe, pode-se atribuir um círculo a um triângulo. 2002 LCG/UFRJ. All rights reserved. 35 Derivação • Derivar classes a partir de uma classe base (herança). ▪ Uma classe derivada herda todas as propriedades da classe base. ▪ Cada classe derivada é uma nova classe. Logo, a classe base não é afetada por mudanças nas classes derivadas. ▪ Uma classe derivada é compatível por tipo com a base, mas o contrário é falso. ▪ Classes irmãs não são compatíveis por tipo. 2002 LCG/UFRJ. All rights reserved. 36 Análise • As primeiras duas opções são típicas de programação procedural. • A terceira opção é aquela que deve ser adotada em programação orientada a objeto. class Derivada: public Base { // membros não listados são herdados. public: // construtores e destrutores (em geral // diferentes da base) // membros da base sobrepostos // novos membros públicos private: // dados adicionais (geralmente privados) // funções privadas adicionais // membros da base a serem desativados }; 2002 LCG/UFRJ. All rights reserved. 37 Uso de Ponteiros com Herança • Ponteiro para classe base ▪ ▪ ▪ ▪ ▪ classeBase* bPtr = NULL; classeBase bVar; Objeto do tipo base. bPtr = &bVar; Trivialmente válido. bPtr = &dVar; dVar é do tipo classeDerivada. bPtr->print(); Chama print da classe base. • Ponteiro para classe derivada ▪ ▪ ▪ ▪ ▪ classeDerivada* dPtr = NULL; classeDerivada dVar; Objeto do tipo derivado. dPtr = &dVar; Trivialmente válido. dPtr = &bVar; ERRO!! dPtr->print(); Chama print da derivada. 2002 LCG/UFRJ. All rights reserved. 38 Apontar Ponteiro Derivado para Classe Base • Gera erro. ▪ Classe derivada pode ter métodos e variáveis que não existem na classe base. ▪ Objeto da classe base não é do mesmo tipo do objeto da classe derivada. 2002 LCG/UFRJ. All rights reserved. 39 Apontar ponteiro Base para Classe Derivada • Mecanismo válido. ▪ Mas só podem ser chamados métodos da classe base. ▪ Chamada a métodos da classe derivada gera erro. • Tipo de um ponteiro ou referência define os métodos que podem ser invocados. 2002 LCG/UFRJ. All rights reserved. 40 Funções Virtuais • Objetos (e não os ponteiros) definem os métodos que devem ser invocados. • Suponha círculo, triângulo e retângulo derivados de uma classe forma. ▪ Cada um com seu próprio método de desenho (draw). • Para desenhar qualquer forma, chama-se draw a partir de um ponteiro para forma. ▪ O programa determina, em tempo de execução, qual draw chamar (formas são tratadas de maneira genérica). 2002 LCG/UFRJ. All rights reserved. 41 Declaração Virtual • Draw deve ser declarada virtual na classe base. ▪ Sobreponha-se draw em cada classe derivada. ▪ As assinaturas devem ser idênticas. ▪ Um vez declarada virtual, será virtual em todas as classes derivadas. ▪ É boa prática manter a declaração virtual nas classes derivadas, embora não seja mandatário. 2002 LCG/UFRJ. All rights reserved. 42 Tipos de Herança • Pública: todos os membros públicos da classe básica permanecem públicos. ▪ Membros privados permanecem privados. ▪ Relacionamento É-Um (IS-A). • Privada: mesmo membros públicos são escondidos. ▪ Relacionamento Tem-Um (HAS-A). ▪ É preferível usar composição do que herança privada. ▪ É a herança default. 2002 LCG/UFRJ. All rights reserved. 43 Herança de Construtores • O estado de público ou privado dos construtores, construtor de cópia, e operador de atribuição são herdados. ▪ Se eles forem herdados, mas não definidos na classe derivada, os operadores correspondentes são aplicados a cada membro. 2002 LCG/UFRJ. All rights reserved. 44 Binding Estático e Dinâmico • No binding estático a decisão de que função invocar para resolver uma sobreposição é tomada em tempo de compilação. • No binding dinâmico a decisão é tomada em tempo de execução. • C++ usa binding estático por default, porque se achava no passado que o overhead seria significativo. • Para forçar o binding dinâmico o programador deve especificar a função como virtual. 2002 LCG/UFRJ. All rights reserved. 45 Porque binding dinâmico? • Quando se declara um ponteiro para uma classe como argumento de uma função, normalmente o tipo do ponteiro é o da classe base. ▪ Assim, pode ser passada qualquer uma das classes derivadas e o tipo do objeto apontado é determinado em tempo de execução. ▪ Virtualidade é herdada. ▪ Funções redefinidas em classes derivadas devem ser declaradas virtuais. ▪ Uma decisão em tempo de execução só é necessária quando o objeto for acessado através de ponteiros. 2002 LCG/UFRJ. All rights reserved. 46 Construtores e Destrutores Virtuais? • Construtores nunca são virtuais. • Destrutores devem ser virtuais na classe base apenas. • O tipo de um construtor sempre pode ser determinado em tempo de compilação. • O destrutor deve ser virtual para garantir que o destrutor do objeto real é chamado. ▪ A classe derivada pode ter membros adicionais que foram alocados dinamicamente, que só podem ser desalocados pelo destrutor da classe derivada. 2002 LCG/UFRJ. All rights reserved. 47 Custo • Polimorfismo tem um custo que impacta na performance. ▪ STL não usa polimorfismo por questão de eficiência. • Cada classe com função virtual tem uma tabela vtable. ▪ Para cada função virtual, vtable tem um ponteiro para função apropriada. ▪ Se a classe derivada tiver a mesma função virtual da classe base, o ponteiro da vtable aponta para a função na base. 2002 LCG/UFRJ. All rights reserved. 48 Casting (compatibilização) • Downcasting ▪ Operador dynamic_cast ◦ Determina o tipo do objeto em tempo de execução. ◦ Retorna 0 se não for o tipo certo (não pode sofrer cast) NewClass *ptr = dynamic_cast < NewClass *> objectPtr; ▪ Pode compatibilizar endereço de objeto do tipo base para ponteiro para classe derivada. 2002 LCG/UFRJ. All rights reserved. 49 Informação de Tipo • Keyword typeid ▪ Header <typeinfo> ▪ Uso: typeid(object) ◦ Retorna type_info do objeto ◦ Tem informação sobre o tipo do operando, incluindo nome. ◦ typeid(object).name() 2002 LCG/UFRJ. All rights reserved. 50 Tipos de Classe • Classes abstratas ▪ Único propósito: ser uma classe base (chamada classe base abstrata). ▪ Incompleta: possui métodos sem código. ◦ Classes derivadas preenchem “pedaços omitidos“. ▪ Objetos não podem ser instanciados a partir de classes abstratas. ◦ No entanto, pode haver ponteiros e referências. • Classes concretas ▪ Podem instanciar objetos. ▪ Implementam todas as funções que definem. ▪ Provêem detalhes. 2002 LCG/UFRJ. All rights reserved. 51 Função Virtual Pura e Regular • Classes abstratas não são necessárias, mas são úteis. ▪ Ponteiros para classes base abstratas são úteis no polimorfismo. • Para criar uma classe abstrata: ▪ Precisa-se de uma ou mais função virtual "pura" ◦ Declare-se função com inicializador 0 virtual void draw() const = 0; ▪ Função virtual regular ◦ Possui implementações, e sobreposição é opcional. ▪ Função virtual pura ◦ Nenhuma implementação, mas deve ser sobreposta. ▪ Classes abstratas podem ter dados e funções concretas. ◦ Devem ter uma ou mais funções virtuais pura. 2002 LCG/UFRJ. All rights reserved. 52 Herdando Interface e Implementação getArea getVolume getName print Shape 0.0 0.0 = 0 = 0 Point 0.0 0.0 "Point" [x,y] Circle pr2 0.0 "Circle" center=[x,y]; radius=r 2pr2 +2prh pr2h "Cylinder" center=[x,y]; radius=r; height=h Cylinder 2002 LCG/UFRJ. All rights reserved. 53 Funções de Desenho • Exemplo clássico em OO: método draw. ▪ Colocar funções de desenho nas classes, requer acesso a um pacote gráfico nas classes (OpenGL,PHIGS,GKS,Direct X...). ▪ Será que é a forma adequada do ponto de vista de portabilidade? • Alternativa melhor pode ser passar os objetos para uma classe de desenho. ▪ Apenas a pessoa que escreve essa classe precisa conhecer Computação Gráfica. 2002 LCG/UFRJ. All rights reserved. 54