Templates 1 Function templates 2 Class templates Com

  • Slides: 16
Download presentation
Templates 1. Function templates 2. Class templates

Templates 1. Function templates 2. Class templates

Com o modelo (template) de função é possível escrever código para, por exemplo, fazer

Com o modelo (template) de função é possível escrever código para, por exemplo, fazer a ordenação de um array de um qualquer tipo. Neste caso, é o C++ que automaticamente gera as várias funções template que ordenam, conforme o tipo do array seja int, float, char ou ainda qualquer outro tipo. Por outro lado, é também possível escrever um único modelo (template) de classe para, por exemplo, uma pilha, e ser depois o C++ que automaticamente gera as várias classes template para os diferentes tipos de pilhas pretendidas, por exemplo, int, float ou char.

Function templates As funções redefinidas são normalmente usadas para executarem operações similares em diferentes

Function templates As funções redefinidas são normalmente usadas para executarem operações similares em diferentes tipos de informação. Os modelos de funções (function templates) podem ser usados de forma mais compacta e conveniente do que as funções redefinidas, isto porque com as funções modelo é criada uma família de funções. O programador escreve apenas uma definição do modelo de função. É baseado nos tipos de argumentos das várias chamadas a este modelo de função que o compilador gera automaticamente funções código/objecto separadas e possivelmente diferentes para assim, responder a cada chamada (tipo) apropriadamente. Em C, isto poderia ser feito usando macros, no entanto isso pode acarretar sérios problemas e não permite ao compilador testar os tipos (type checking). Os modelos de funções permitem uma solução compacta como as macros, mas permitem também testar os tipos dos argumentos (full type checking).

Todas as definições de modelos de funções começam pela palavra chave template seguida por

Todas as definições de modelos de funções começam pela palavra chave template seguida por uma lista de parâmetros, delimitados pelos caracteres ‘<‘ e ‘>’. Cada parâmetro formal tem de ser precedido pela palavra chave class como em: template < class M > ou template < class Tipo_de_Elemento > ou template < class M, class C >

Os parâmetros formais de uma definição de template são usados (como no caso de

Os parâmetros formais de uma definição de template são usados (como no caso de tipos pré-definidos ou tipos definidos pelo utilizador) para especificar os tipos dos argumentos da função, o tipo de retorno da função e para declarar variáveis dentro da função. A definição da função propriamente dita, vem a seguir e é definida como normalmente se faz.

Class templates Quando estamos em situações onde se pode fazer a reutilização de software

Class templates Quando estamos em situações onde se pode fazer a reutilização de software temos de ter um meio de genericamente descrever classes que são versões especificas dessa classe geral. Esta capacidade é fornecida em C++ pelos modelos de classes. Um modelo de classe especifica como se podem construir classes individuais tal como uma declaração de classe especifica como se podem construir objectos individuais. Os modelos de classes são também chamados tipos parametrizados porque requerem um ou mais tipos de parâmetros para especificar como um modelo de classe se pode transformar numa classe específica. O programador que desejar usar modelos de classes tem simplesmente de escrever a definição de um modelo de classe. De cada vez que o programador necessita de uma classe modelo de um tipo específico, o programador usa uma notação simples e concisa para invocar o modelo de classe após a qual o compilador gera o código necessário para a classe modelo que o programador necessita.

Vamos agora ver um exemplo onde com um modelo de classe podemos definir uma

Vamos agora ver um exemplo onde com um modelo de classe podemos definir uma pilha de elementos de um tipo arbitrário: template<class M> class pilha { M* m; M* p; int tamanho; public: pilha ( int t ) { m = p = new M [ tamanho = t ] ; } ~pilha ( ) { delete [ ] m ; } void push (M a) { *p++ = a; } M pop ( ) { return *--p ; } int size ( ) const { return p - m; } };

A definição de um modelo de classe é parecida com a definição de uma

A definição de um modelo de classe é parecida com a definição de uma classe convencional, excepto o facto de ser precedida do cabeçalho: template < class M > Este indica que se trata de uma definição de um modelo de classe com parâmetro tipo M, (M pode ser qualquer tipo, não tem de ser uma classe) o qual indica o tipo de classe modelo a ser criada. No exemplo seguinte M passou a ser char. Note-se também que o nome de um modelo de classe seguido dos parêntises < > é o nome de uma classe (definida pelo modelo) e pode ser usada como qualquer outro nome de classe. pilha < char > pc (10) ; //pilha de caracteres Aqui define-se um objecto pc da classe pilha < char >.

Esta classe modelo, a menos do nome pilha < char >, funciona da mesma

Esta classe modelo, a menos do nome pilha < char >, funciona da mesma maneira de que funcionaria se tivesse sido escrita na forma: class pilha_char { char* m; char* p; int tamanho; public: pilha ( int t ) { m = p = new char [ tamanho = t ] ; } ~pilha ( ) { delete [ ] m ; } void push (char a) { *p++ = a } char pop ( ) { return *--p ; } int size ( ) const { return p - m; } }; Vamos ver como podemos usar templates.

Examinemos o exemplo de um modelo de função para imprimir o maior valor de

Examinemos o exemplo de um modelo de função para imprimir o maior valor de um conjunto. O modelo de função, imprime_maior, declara como parâmetro formal apenas o parâmetro M (M podia ser qualquer outro identificador válido, aqui foi escolhido M), que define o tipo do conjunto a ser analisado pela função imprime_maior. Assim, temos: template < class M > void imprime_maior (M *conjunto, const int tamanho) { M temp = 0; for (int i = 0; i < tamanho; i++) { if(temp<conjunto[i]) temp = conjunto[i]; } cout << temp << endl; }

M é referido como o tipo de parâmetro. Quando o compilador detecta uma chamada,

M é referido como o tipo de parâmetro. Quando o compilador detecta uma chamada, no código do programa fonte, da função imprime_maior, o tipo M vai ser substituído pelo tipo do parâmetro que está na invocação da função. Após isto, o C++ cria uma função modelo para imprimir um conjunto do tipo especificado. Esta função recém criada é depois compilada. A seguir, apresenta-se um programa com três chamadas ao modelo de função, em que os parâmetros são no primeiro caso um inteiro, no segundo um float e no terceiro um char. imprime_maior ( a, tamanho_a ); //imprime o maior valor de a, a é de tipo int* imprime_maior ( b, tamanho_b ); //imprime o maior valor de b, b é de tipo float* imprime_maior ( c, tamanho_c ); //imprime o maior valor de c, c é de tipo char*

Estas chamadas são convertidas em código pelo C++ o que resulta, por exemplo para

Estas chamadas são convertidas em código pelo C++ o que resulta, por exemplo para o caso de tipo int, em: void imprime_maior (int *conjunto, const int tamanho) { int temp = 0; for (int i = 0; i < tamanho; i++) { if(temp<conjunto[i]) temp = conjunto[i]; } cout << temp << endl; } Algo a notar, é que cada parâmetro formal na definição do modelo da função tem de aparecer na lista de parâmetros da função pelo menos uma vez. Por outro lado o nome de um parâmetro formal só pode ser escrito uma vez na lista de parâmetros do cabeçalho do modelo. No entanto os nomes dos parâmetros formais, nos vários modelos de função que possam existir, não têm de ser únicos.

Vejamos alguns exemplos: template<class M> void imprime(M); //correcto template<class M, class A> int imprime(M*,

Vejamos alguns exemplos: template<class M> void imprime(M); //correcto template<class M, class A> int imprime(M*, int, A); //correcto template<class M> void imprime(int); //erro, o parâmetro M // não aparece na lista de parâmetros da função template<class M, class A> char converte(M); //erro, o parâmetro A // não aparece na lista de parâmetros da função template<class M, class. A> A converte(M); // erro, o parâmetro A // não aparece na lista de parâmetros da função template<class M, class B, class M> int converte(M, B); // erro, // o parâmetro M aparece duas vezes na lista de parâmetros // do modelo

Um ponto importante no uso de modelos é a reutilização de software, mas como

Um ponto importante no uso de modelos é a reutilização de software, mas como podem ser criadas, num programa, múltiplas cópias de funções modelos, para outras tantas invocações da mesma, temos de ter em conta a memória “consumida” por estas cópias. Ao analisar a função descrita, reparamos que tudo corre bem até à função modelo com parâmetros char*, onde se nota que o operador < não está definido para tal tipo, pelo menos da forma pretendida, o que pode levar a resultados surpreendentes. Isto acontece porque alguns operadores não estão definidos para certos tipos, ou tipos criados pelo utilizador, ou estão mas não fazem o que é pretendido em dado momento. Portanto, é necessário fazer a redefinição desses operadores para o tipo definido pelo utilizador, sempre que esses operadores são usados sobre objectos desse tipo dentro da função modelo.

Olhando agora para o nosso exemplo, verifica-se que é necessário fazer a redefinição do

Olhando agora para o nosso exemplo, verifica-se que é necessário fazer a redefinição do operador <, ou então, criar uma definição especial da função imprime_maior para este caso específico. Esta forma de resolver o problema, criando definições de funções modelo separadas, para estes casos especiais dá um elevado grau de flexibilidade e pode ser uma importante ferramenta para alcançar uma boa performance.

A definição especial, no nosso caso seria: void imprime_maior (char **conjunto, const int tamanho)

A definição especial, no nosso caso seria: void imprime_maior (char **conjunto, const int tamanho) { char* temp = conjunto[0]; for (int i = 1; i < tamanho; i++) { if(strcmp(conjunto[i], temp)>0) temp = conjunto[i]; } cout <<temp<< endl; };