7 Abstração Genérica Unidades genéricas e instanciação. Tipos e classes parâmetros. Notas de implementação. 7-1 Unidades genéricas e instanciação (1) Uma unidade genérica é uma unidade de programa que é parametrizada em relação às entidades das quais ela depende A instanciação de uma unidade genérica cria uma unidade de programa ordinária, na qual, cada parâmetro formal da unidade é trocado por um argumento • Gera sob demanda uma família de unidades de programas similares, evitando a redundância de código • Favorece a reusabilidade porque uma mesma unidade de programa pode ser instanciada em diferentes programas 7-2 Unidades genéricas e instanciação (2) Unidades genéricas seguem o princípio da abstração • Uma unidade genérica é uma abstração sobre uma declaração – Possui um corpo – a declaração – e uma instanciação genérica é uma declaração que produzirá ligações pela elaboração do corpo da unidade genérica Unidades genéricas são suportadas por Ada, C++ e Java em diferentes perspectivas 7-3 Exemplo: pacotes genéricos em Ada (1) Ada suporta pacotes genéricos e procedimentos genéricos generic capacity: Positive; package Queues is Parâmetro formal baseado em valor type Queue is limited private; procedure clear (q: out Queue); procedure add (q: in out Queue; e: in Character); procedure remove (q: in out Queue; e: out Character); private type Queue is record length: Integer range 0 .. capacity; front, rear: Integer range 0 .. capacity-1; elems: array (0 .. capacity-1) of Character; end record; end Queues; 7-4 Exemplo: pacotes genéricos em Ada (2) package body Queues is procedure clear (q: out Queue) is begin q.front := 0; q.rear := 0; q.length := 0; end; procedure add (q: in out Queue; e: in Character) is begin q.elems(q.rear) := e; q.rear := (q.rear + 1) mod capacity; q.length := q.length + 1; end; procedure remove (q: in out Queue; e: out Character) is begin e := q.elems(q.front); q.front := (q.front + 1) mod capacity; q.length := q.length - 1; end; end Queues; 7-5 Exemplo: pacotes genéricos em Ada (3) Pacotes genéricos em Ada são instanciados por uma forma especial de declaração denominada instantiation • • package Line_Buffers is new Queues(120); package Input_Buffers is new Queues(80); Argumentos da instanciação A instanciação resulta num pacote ordinário • inbuf: Input_Buffers.Queue; ... Input_Buffers.add(inbuf, '*'); 7-6 Exemplo: pacotes genéricos em C++ (1) C++ suporta funções genéricas (function templates) e classes genéricas (class templates) template <int capacity> class Queue { Parâmetro formal da classe genérica – denota um valor inteiro a ser conhecido durante a instanciação private: char elems[capacity]; int front, rear, length; public: Queue (); void add (char e); char remove (); } 7-7 Exemplo: pacotes genéricos em C++ (2) Definição de construtores e métodos em separado template <int capacity> Queue<capacity>::Queue () { front = rear = length = 0; } template <int capacity> void Queue<capacity>::add (char e) { elems[rear] = e; rear = (rear + 1) % capacity; length++; } template <int capacity> char Queue<capacity>::remove () { char e = elems[front]; front = (front + 1) % capacity; length--; return e; } 7-8 Exemplo: pacotes genéricos em C++ (3) Instanciação de pacotes genéricos • typedef Queue<80> Input_Buffer; typedef Queue<120> Line_Buffer; A instanciação resulta em classes nas quais os parâmetros formais são substituídos pelo valor do argumento • Input_Buffer inbuf; Line_Buffer outbuf; Line_Buffer errbuf; • ou • Queue<80> inbuf; Queue<120> outbuf; Queue<120> errbuf; 7-9 Exemplo: pacotes genéricos em C++ (4) A instanciação on the fly de classes genéricas resulta num problema conceitual e em outro de ordem pragmática • O problema conceitual diz respeito a equivalência de tipos – Duas variáveis outbuf e errbuf declaradas com tipos Queue<120> e Queue<120> são equivalentes – Mas se duas variáveis são declaradas com tipos Queue<m> e Queue<n-1>, o compilador não tem como decidir se os tipos são equivalentes Estes problemas não ocorrem em Ada! – instanciações em C++ devem obrigatoriamente poder avaliar seus argumentos em tempo de compilação • O problema pragmático pode levar o programador a perder o controle sobre a expansão de código – Se Queue<120> ocorre em vários locais do código, um compilador não profissional pode gerar várias instâncias de Queue, enquanto um 7-10 profissional geraria apenas uma única instância Tipos e classes parâmetros Como uma unidade de programa usa um valor definido em qualquer lugar, a unidade de programa pode tornar-se genérica e parametrizada com relação a esse valor • Graças ao princípio da correspondência Como uma unidade de programa usa um tipo (ou classe) definido em qualquer lugar, a unidade de programa pode tornar-se genérica e parametrizada com relação a esse tipo • Tem-se uma nova modalidade de parâmetro: o parâmetro que é um tipo (ou uma classe) Unidade genéricas em Ada, C++ e Java podem ter tipos como parâmetros 7-11 Exemplo: tipo parâmetro em Ada (1) Uma unidade genérica em Ada pode ser parametrizada com respeito a qualquer tipo do qual ela dependa Parâmetro formal do pacote genérico generic type Element is private; – denota um tipo a ser conhecido package Lists is durante a instanciação type List is limited private; procedure clear (l: out List); procedure add (l: in out List; e: in Element); ... private capacity: constant Integer := ...; type List is record length: Integer range 0 .. capacity; elems: array (1 .. capacity) of Element; end record; end Lists; 7-12 Exemplo: tipo parâmetro em Ada (2) O parâmetro formal Element é usado tanto da especificação do pacote, quanto no seu corpo package body Lists is procedure clear (l: out List) is begin l.length := 0; end; procedure add (l: in out List; e: in Element) is begin l.length := l.length + 1; l.elems(l.length) := e; end; ... end Lists; 7-13 Exemplo: tipo parâmetro em Ada (3) Instanciação do pacote genérico Lists – package Phrases is new Lists(Character); • O parâmetro formal Element é ligado ao tipo Character (argumento na instanciação) • Depois, a especificação e o corpo de Lists são elaborados, gerando um pacote que encapsula uma lista com elementos do tipo Character – sentence: Phrases.List; ... Phrases.add(sentence, '.'); • Outra opção – type Transaction is record ... end record; ... package Transaction_Lists is new Lists(Transaction); 7-14 Exemplo: tipo e função parâmetro em Ada (1) Se uma unidade genérica não sabe nada sobre o tipo parâmetro – exceto o nome – ela ainda assim pode declarar variáveis desse tipo, mas não pode aplicar nenhuma operação sobre tais variáveis! • Unidades genéricas devem saber pelo menos algumas das operações aplicáveis ao tipo parâmetro – Tipicamente, as operações de atribuição e de teste de igualdade 7-15 Exemplo: tipo e função parâmetro em Ada (2) generic type Element is private; with function precedes (x, y: Element) return Boolean; package Sequences is Parâmetro formal que type Sequence is limited private; procedure clear (s: out Sequence); denota a uma função desconhecida que recebe dois argumentos do tipo Element e retorna um resultado do tipo Boolean procedure append (s: in out Sequence; e: in Element); procedure sort (s: in out Sequence); private capacity: constant Integer := ...; type Sequence is record length: Integer range 0 .. capacity; elems: array (1 .. capacity) of Element; end record; end Sequences; 7-16 Exemplo: tipo e função parâmetro em Ada (3) O corpo do pacote usa o tipo Element e a função precedes, parâmetros da unidade genérica package body Sequences is ... procedure sort (s: in out Sequence) is e: Element; begin ... if precedes(e, s.elems(i)) then ... ... end; end Sequences; 7-17 Exemplo: tipo e função parâmetro em Ada (4) Possível instanciação de Sequences type Transaction is record ... end record; function earlier (t1, t2: Transaction) return Boolean; -- Return true if and only if t1 has an earlier timestamp than t2. package Transaction_Sequences is new Sequences(Transaction, earlier); que pode ser usada como um pacote comum audit_trail: Transaction_Sequences.Sequence; ... Transaction_Sequences.sort(audit_trail); Outros exemplos package Ascending_Sequences is new Sequences(Float, "<"); readings: Ascending_Sequences.Sequence; ... Ascending_Sequences.sort(readings); 7-18 Exemplo: tipo e função parâmetro em Ada (5) Em geral, se uma unidade genérica em Ada tem um tipo parâmetro T, ele é especificado pela cláusula • type T is especificação das operações que equipam T; O compilador verifica a unidade genérica para assegurar que • operações usadas por T na unidade genérica nas operações com as quais T está equipada O compilador verifica separadamente cada instanciação da unidade genérica para assegurar que • operações com as quais T está equipada nas operações que equipam o tipo que é passado como argumento Uma vez implementada, as unidades genéricas em Ada podem ser reusada com segurança em relação a verificação 7-19 de tipos Exemplo: tipo parâmetro em C++ (1) Unidades genéricas em C++ podem ser parametrizadas em relação a quaisquer tipos ou classes das quais elas dependam template <class Element> class Sequence is private: const int capacity = ...; int length; Element elems[capacity]; Parâmetro formal que denota a um tipo qualquer desconhecido, e não apenas tipos que são classes public: Sequence (); void append (Element e); void sort (); } 7-20 Exemplo: tipo parâmetro em C++ (2) Implementação segue como usual template <class Element> void Sequence<Element>::sort () { Element e; ... if (e < elems[i]) ... ... } • A utilização do operador "<" é perfeitamente legal, pois assume-se que o tipo denotado por Element "estará" equipado com tal operador – O compilador rejeitará instanciações para tipos argumentos que não sejam equipados com o operador "<" 7-21 Exemplo: tipo parâmetro em C++ (3) Exemplos de instanciações typedef Sequence<float> Number_Sequence; Number_Sequence readings; ... readings.sort(); O operador "<" será utilizado, mas o resultado não será o desejado! Por que? typedef char* String; typedef Sequence<String> String_Sequence; struct Transaction { ... }; typedef Sequence<Transaction> Transaction_Sequence; Esta instanciação será recusada pelo compilador, pois o operador "<" não equipa o tipo Transaction 7-22 Exemplo: tipo parâmetro em C++ (4) Em geral, se uma unidade genérica em C++ tem um tipo parâmetro T, ele é especificado pela cláusula • <class T> que não revela nada sobre as operações que equipam T. O compilador verifica cada instanciação da unidade genérica para assegurar que • operações usadas por T na unidade genérica nas operações que equipam o tipo que é passado como argumento As unidades genéricas de C++ não são uma fundação segura para reuso de software • O compilador não é capaz de verificar completamente os tipos numa definição de unidade genérica, mas apenas instanciações individuais 7-23 Exemplo: classe genérica em Java com classe parâmetro (1) Unidades genéricas foram introduzidas em Java com o lançamento da plataforma Java 2SE 5.0 em 2004 • Classes genéricas – classes que podem ser parametrizadas em relação a outras classes class List <Element> { private int length; private Element[] elems; public List () { ... } Parâmertro formal da classe genérica List e denota uma classe desconhecida a priori public void append (Element e) { ... } ... } 7-24 Exemplo: classe genérica em Java com classe parâmetro (2) Instanciação de classes genéricas List<Character> sentence; List<Transaction> transactions; O argumento na instanciação deve ser uma classe – tipos primitivos são proibidos List<char> sentence; Instanciação ilegal! 7-25 Exemplo: classe genérica em Java com classe parâmetro limitada pela interface (1) Caso uma classe genérica assuma que a classe parâmetro está equipada com operações, a classe parâmetro deve ser especificada como implementando uma interface adequada • Diz-se que tal classe parâmetro é limitada (bounded) pela interface class Sequence <Element implements Comparable<Element>> { private int length; private Element[] elems; public Sequence () { ... } public void append (Element e) { ... } public void sort () { Element e; ... if (e.compareTo(elems[i] < 0) ... ... } } Element é um parâmetro formal que denota uma classe desconhecida, mas que deve implementar a interface Comparable, onde o método compareTo está 7-26 especificado Exemplo: classe genérica em Java com classe parâmetro limitada pela interface (2) Possível instanciação da classe genérica Sequence Sequence<Transaction> auditTrail; ... auditTrail.sort(); assumindo que a classe Transaction é declarada assim Class Transaction implements Comparable<Transaction> { private ...; public int compareTo(Transaction that) { ... } ... } 7-27 Exemplo: classe genérica em Java com classe parâmetro limitada pela interface (3) Em geral, se uma unidade genérica em Java tem uma classe Desvantagens da classes genéricas emcláusula Java parâmetro C, ela é especificada pela • C implements Interface; • Somente classes podem ser parâmetros de classes O compilador verifica a unidade genérica para assegurar que genéricasusadas por C na unidade genérica nas operações • operações declaradas em Interface • Tipos primitivos são proibidos – conseqüência da incompletude O compilador verifica separadamente cada instanciação da unidadedegenérica para assegurar que tipos em Java • operações declaradas na Interface nas operações que equipam a classe passada como argumento • Não podem ser parametrizadas por valores dos quais Uma vez implementada, as unidades genéricas em Java dependam podem ser reusadas com segurança em relação a verificação 7-28 de tipos Notas de Implementação Unidades genéricas com tipos parâmetros suscitam interessantes problemas de implementação • Como o tipo parâmetro denota um tipo desconhecido, o compilador não pode determinar, apenas da unidade genérica, como os valores do tipo serão representados – Não há como saber o espaço de memória requerido, por exemplo • Somente quando a unidade genérica é instanciada é que o compilador pode saber essa informação 7-29 Exemplo: implementação de unidades genéricas em Ada (1) 7-30 Exemplo: implementação de unidades genéricas em Ada (2) As três instanciações anteriores definem três tipos distintos • A.List • B.List • C.List Em geral, cada instanciação de um pacote genérico de Ada é compilado, com a geração de código objeto especializado para cada instanciação • Em casos específicos, diferentes instanciações podem compartilhar o mesmo código objeto – quando os tipos envolvido têm representações similares 7-31 Exemplo: implementação de unidades genéricas em C++ Similar a implementação em Ada, mas com uma complicação extra • As classes genéricas de C++ podem ser instanciadas on the fly – Um código de programa pode conter várias declarações de variáveis do tipo List<float> – No sistema de tipos de C++, todas estas variáveis têm o mesmo tipo, logo estão equipadas com as mesmas operações – O desafio para o compilador é fazer com que todas estas variáveis compartilhem o mesmo código objeto 7-32 Exemplo: implementação de unidades genéricas em Java (1) A implementação de classes genéricas em Java é simplificada porque elas só podem ser parametrizadas em relação a classes, que sempre são acessadas através de apontadores 7-33