C++
Home

Gabaritos
Sobrecarregando Gabaritos
Gabaritos de Classes
Gabaritos de classe e parâmetros não-tipo
Gabaritos e Herança
Gabaritos e friends
Gabaritos e membros static


Gabaritos

Funções sobrecarregadas são normalmente usadas para executar operações semelhantes sobre tipos de dados diferentes. Se as operações são idênticas para cada tipo de, isto pode ser executado mais compacta e convenientemente usando-se gabaritos de função.

Todas as definições de gabaritos começam com a palavra-chave template seguida por uma lista de parâmetros de tipos formais para o gabarito de função.

Exemplo

#include <iostream>

using std::cout;
using std::endl;

template< class Tipo >
void imprimir_array( const Tipo *varray, const int total )
{
  for ( int i=0; i<total; i++ )
  { 
    cout << varray[i] << " ";
  }

  cout << endl;
}

int main()
{
  const int total1 = 5, total2 = 7, total3 = 4;

  int a[total1] = { 1, 2, 3, 4, 5 };
  double b[total2] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7 };
  char c[total3] = "ALO";

  cout << "Arrays " << endl;

  imprimir_array( a, total1 );

  cout << "Arrays " << endl;
  imprimir_array( b, total2 );

  cout << "Arrays " << endl;
  imprimir_array( c, total3 );

  system("pause");
  return 0;
}


Sobrecarregando Gabaritos

Podemos sobrecarregar funções gabaritos de modo que o compilador usa a resolução de sobrecarga para invocar a função apropriada.
Por exemplo, a função imprimir array poderia ter parâmetros adicionais como maior índice e menor índice para varrer apenas entre alguns índices.


Gabaritos de Classes

É possível compreender o que é uma pilha, independente do tipo de itens que estão sendo colocados nela. Mas quando chega a hora de instancia-las um tipo deve ser especificado. Isto cria uma oportunidade maravilhosa para a reutilização de software. Necessitados dos meios de descrever a noção de uma pilha genericamente e instanciar classes que são versões desta classe genérica para tipos específicos. Este recursos é fornecido por gabaritos em C++.

Gabaritos de classes são chamados de tipos parametrizados, por que exigem um ou mais parâmetros de tipo para especificar como personalizar um gabarito de uma classe genérica para formar um gabarito de classe especifico.

Vamos demonstrar o exemplo da pilha.
Existem duas restrições para tipos de dados não primitivos no exemplo: eles devem ter um construtor default e devem suportar o operador de atribuição.

As definições de funções das classes gabaritos devem ficar no arquivo .h

// tstatck.h

#ifndef TSTACK_H
#define TSTACK_H

// uma stack é uma pilha

template< class Tipo >
class Pilha {

  public :
    Pilha( int = 10 );
    ~Pilha();
    //~Pilha() { delete [] ponteiro; }

    // push = inserir
    bool push( const Tipo & );

    // pop = excluir
    bool pop( Tipo & );

  private:
    int tamanho;
    int topo;

    // ponteiro para pilha
    Tipo *ponteiro;

    //bool vazio() const { return topo == -1; };
    bool vazio() const;

    //bool cheio() const { return topo == tamanho - 1; }
    bool cheio() const;
};

// definiçoes em .h

template< class Tipo >
Pilha< Tipo >::Pilha( int vtamanho)
{
  tamanho = vtamanho > 0 ? vtamanho : 10;

  // inicialmente a pilha está fazia
  topo = -1;

  // aloca espaço para elementos
  ponteiro = new Tipo[ tamanho ];
}

// destrutor
template< class Tipo >
Pilha< Tipo >::~Pilha()
{
  delete [] ponteiro;
}

// Insere um elemento na pilha
template< class Tipo >
bool Pilha< Tipo >::push( const Tipo &novo_item )
{
  if ( !cheio() )  
  {  
    ponteiro[ ++topo ] = novo_item;
    return true;
  }

  return false;
}

template< class Tipo >
bool Pilha< Tipo >::pop( Tipo &item )
{
  if ( !vazio() ) 
  { 
    item = ponteiro[ topo-- ];
    return true;
  }

  return false;
}

template< class Tipo >
bool Pilha< Tipo >::vazio() const
{
  return topo == -1;
}

template< class Tipo >
bool Pilha< Tipo >::cheio() const
{
  return topo == tamanho - 1;
}

#endif

 

// main.cpp

#include <iostream>

using std::cout;
using std::cin;
using std::endl;

#include "tstack.h"

int main()
{

  Pilha< double > Tdouble_pilha( 5 );
  double f = 1.1;

  cout << "Inserindo elementos " << endl;

  while ( Tdouble_pilha.push( f ) )
  {  
     cout << f << " ";
     f += 1.1;
  }  

  cout << "\n\nA pilha ficou cheia " << endl;
  cout << "Retirando elementos" << endl;

  while ( Tdouble_pilha.pop( f ) )
  {  
    cout << f << " ";
  }

  cout << "\n\nA pilha ficou vazia " << endl;

  Pilha< int > Tint_pilha( 5 );
  int a = 1;

  cout << "Inserindo elementos " << endl;

  while ( Tint_pilha.push( a ) )
  {  
    cout << a << " ";
    a += 1;
  } 

  cout << "\n\nA pilha ficou cheia " << endl;
  cout << "Retirando elementos" << endl;

  while ( Tint_pilha.pop( a ) )
  {  
    cout << a << " ";
  }

  cout << "\n\nA pilha ficou vazia " << endl;

  system("pause");

  return 0;
}

 



Gabaritos de classe e parâmetros não-tipo

O gabarito de classe stack( pilha ) da seção anterior usou somente parâmetros de tipo no cabeçalho do gabarito. Também é possível usar parâmetros não-tipo; um parâmetro não tipo pode ter um argumento default e o parâmetro é tratado como const.

Exemplo:

template< class Tipo, int elements >

declarado como

Pilha< double, 100 > vnossa_pilha;

Durante a compilação, pilha será declarada como um double de 100 elementos.

Isso elimina, quando possivel, o overhead de criar espaço com new.


Gabaritos e Herança

-> Um gabarito de classe pode ser derivado de uma classe gabarito.

-> Um gabarito de classe pode ser derivado de uma classe não-gabarito.

-> Uma classe gabarito pode ser derivada de um gabarito de classe.

-> Uma classe não-gabarito pode ser derivada de um gabarito de classe.


Gabaritos e friends

-> Dentro de um gabarito de classe para a classe X que foi declarado com

template< class Tipo > class X

uma declaração de friend da forma

friend void f1().

torna a função f1 um friend de toda classe gabarito instanciadas a partir do gabarito de classe precedente.



-> Dentro de um gabarito de classe para a classe X que foi declarado com

templace< class Tipo > class X

uma declaração de friend da forma

friend void f2( X< Tipo >& );

para um tipo particular Tipo, tal como float, torna uma função f2 um friend somente de X<float>.



-> Dentro de um gabarito de classe, você pode declarar que uma função membro de outra classe é um friend de qualquer classe gabarito gerada a partir do gabarito de classe. Simplesmente nomeie a função membro de uma outra classe usando o nome de classe e o operador de resolução de escopo binário.

template< class Tipo > class X

uma declaração de friend da forma

friend void A::f4();

torna a função membro f4 da classe A um friend de toda classe gabarito instanciada a partir do gabarito de classe precedente.



-> Dentro de um gabarito de classe para a classe X que foi declarado com

template< class Tipo > classe X

uma declaração de friend da forma

friend void C < Tipo >::f5( X< Tipo > & );

para um tipo particular Tipo, tal como float, torna a função membro

C< float >::f5( X< float > & )

uma função friend somente da classe gabarito X< float >.



-> Dentro de um gabarito de classe para a classe X que foi declarado com

template< class Tipo > class X

uma segunda classe Y pode ser declarada com

friend class Y;

tornando toda função membro da classe Y um friend de toda classe gabarito produzida a partir do gabarito de classe para X.



-> Dentro de um gabarito de classe para a classe X que foi declarado com

template< class Tipo > class X

uma segunda classe Z pode ser declarada com

friend class Z< T >;

então, quando uma classe gabarito é instanciada com um tipo particular para Tipo, tal como float, todos os membros de class Z< float > tornam-se friends da classe gabarito X< float >


Gabaritos e membros static

Cada classe instanciada a partir de um gabarito de classe tem sua propria copia de cada membro de dados static do gabarito de classe; todos os objetos daquela classe compartilham aquele membro de dados static. O membro deve ser iniciado no escopo do arquivo.