FAQ Lite
Classes Containers e Templates

[ 31.1 ] Como eu faço uma matriz associativa, perl-like, em C++?
[ 31.2 ] Como eu posso construir um <container favorito> de objetos de diferentes tipos?
[ 31.3 ] Como eu posso inserir/acessar/alterar elementos de uma lista ligada/hashtable?
[ 31.4 ] Qual a idéia por trás de templates?
[ 31.5 ] Qual é a sintaxe/semântica para uma template de função?
[ 31.6 ] Qual a sintaxe/semântica para uma template de classe?
[ 31.7 ] O que é um tipo parametrizado?
[ 31.8 ] O que é genericidade?

[ 31.1 ] Como eu faço uma matriz associativa, perl-like, em C++?

Use a classe template padrão map<Key,val>:
    #include <string>
    #include <map>
    #include <iostream>
    using namespace std;
    
    main()
    {
      map<string,int,less<string> >  age;   // age is a map from string to int
    
      age["Fred"] = 42;                     // Fred is 42 years old
      age["Barney"] = 37;                   // Barney is 37
    
      if (todayIsFredsBirthday())           // On Fred's birthday,
        ++ age["Fred"];                     // increment Fred's age
    
      cout << "Fred is " << age["Fred"] << " years old\n";
    } 
Topo
[ 31.2 ] Como eu posso construir um <container favorito> de objetos de diferentes tipos?

Você não pode, mas pode simular isso muito facilmente. Em C/C++ todas as matrizes são homogêneas, ou seja, os elementos têm todos o mesmo tipo. Entretetanto, com uma camada extra de acesso indireto a memória, você pode lhes dar a aparência de serem containers heterogêneos (um container heterogêneo é um container que contém objetos de diferentes tipos).

Há dois casos especiais com containers heterogêneos:

O primeiro ocorre quando todos os objetos que você quer armazenar em um container são derivados publicamente de uma classe base comum. Você pode então declarar/definir seu container para conter pointers para a classe base. Você armazena indiretamente um objeto de classe derivada em um container armazenando o endereço do objeto em um elemento do container. Você pode então acessar indiretamente os objetos do container através dos pointers (aproveitando o comportamento polimórfico). Se você precisar saber o tipo específico de um objeto armazenado no container, você poderá usar dynamic_cast<> ou typeid(). Provavelmente você vai precisar do idioma de construtuores virtuais para copiar um container de objetos distintos.

O lado ruim dessa abordagem é que o gerenciamento de memória torna-se um pouco mais complicado (a quem pertencem os objetos apontados? se você delete os objetos apontados quando você destrói o container, como você pode garantir que ninguém mais tem uma cópias desses ponters? se você não delete os objetos apontados quando destrói o container, como você pode estar certo de alguém vai eventualmente fazer o delete?). Essa abordagem ainda torna a processo de cópia do container mais complexo (pode-se realmente interromper as funções de cópia do container, desde que você não queira copiar os pointers, pelo menos não quando o container possui os objetos apontados).

O segundo caso especial ocorre quando os tipos dos objetos são desarticulados - não compartilham uma mesma classe base. A abordagem aqui é usar uma handle class. O container é um recipiente de controladores de objetos (por valor ou por pointer, a escolha é sua; por valor é mais fácil). Cada controlador de objeto sabe como controlar (manter um pointer para) um dos objetos que você quer colocar no container. Você pode usar tanto uma handle class única com vários e diferentes tipos de pointers como dados de instância, quanto uma hierarquia de handle classes que refletem os vários tipos que você deseja armazenar, isso requer que o container seja um recipiente de pointers da classe base (handle class). O lado negativo dessa abordagem é obrigar a manutenção da handle class (ou handle classes) cada vez que você precisar alterar o conjunto de tipos a ser armazenado no container. O benefício é que você pode usar a(s) handle class(es) para encapsular a maior parte do trabalho de gerenciamento de memória e controle de existência dos objetos. Usar controladores de objetos pode ser benéfico mesmo no primeiro desses dois casos.

Topo
[ 31.3 ] Como eu posso inserir/acessar/alterar elementos de uma lista ligada/hashtable?

Eu vou usar um caso de inserção em uma lista ligada como um protótipo. É mais fácil permitir inserção apenas no topo ou no final da lista, mas limitarmo-nos a isso produziria uma biblioteca muito fraca (uma biblioteca fraca é quase pior do que nenhuma biblioteca).

Essa resposta será de dificil compreensão para novatos em C++, então eu vou dar algumas opções. A primeira opção é a mais fácil, a segunda e a terceira são melhores.

  1. Reforce a List com uma localização corrente, e funções membro tais como advance(), backup(), atEnd(), atBegin(), getCurrElem(), setCurrElem(Elem), insertElem(Elem) e removeElem(). Embora isso funcione em exemplos pequenos, a noção de uma posição corrente dificulta o acesso a elementos em duas ou mais posições da lista (por exemplo, para todos os pares x,y faça o seguinte ...)
  2. Remova de List as funções membro mencionadas acima, e coloque-as em uma classe em separado, ListPosition. ListPosition funcionaria como um posição corrente dentro da lista. Isso permite múltiplas posições dentro da mesma lista. ListPosition seria uma friend da
    class List
    , de modo que List pode ocultar suas partes internas (senão as partes internas de List teriam que ser publicadas através das funções membro public de List).
    Nota: ListPosition pode usar sobrecarga de operator para funções tais como advance() e backup(), já que sobrecarga de operador adoça a sintaxe de funções membro normais.
  3. Considere toda a interação como um único evento, e crie uma classe template para incorporar esse evento. Isso melhora a performance porque possibilita evitar acesso a funções membro públicas (que podem ser funções virtual) durante o loop interno. Infelizmente você terá código objeto extra no programa de aplicação, já que templates geram velocidade duplicando código.
Topo
[ 31.4 ] Qual a idéia por trás de templates?

Uma template é como um cortador de biscoitos que especifica como cortar os biscoitos para que todos sejam muito parecidos (embora os biscoitos possam ser feitos de vários tipos de massa, eles terão todos a mesma forma básica). Da mesma forma, uma template de classe é um cortador de biscoitos para a descrição de como construir uma família de classes de tal forma que todas se pareçam basicamente a mesma, e uma template de função descreve como construir uma família de funções que pareçam similares.

Templates de classes são usadas normalmente para construir container com controle de tipos, embora isso seja muito pouco perto do que se fazer com templates.

Topo
[ 31.5 ] Qual é a sintaxe/semântica para uma template de função?

Considere essa função que inverte seus dois argumentos inteiros:
    void swap(int& x, int& y)
    {
      int tmp = x;
      x = y;
      y = tmp;
    } 
Se nós tivéssemos também que inverter float, longs, Strings, Sets e FileSystems, ficaríamos um tanto cansados de escrever linhas de código praticamente iguais exceto pelo tipo do dado. Repetição simples é o trabalho ideal para um computador, por isso uma template de função: 
    template<class T>
    void swap(T& x, T& y)
    {
      T tmp = x;
      x = y;
      y = tmp;
    } 
A cada vêz que usarmos swap() com um determinado par de tipos de dados, o compilador irá até a definição acima e criará uma outra função template, como uma instanciação da função acima. Por exemplo:
    main()
    {
      int    i,j;  /*...*/  swap(i,j);  // Instantiates a swap for int
      float  a,b;  /*...*/  swap(a,b);  // Instantiates a swap for float
      char   c,d;  /*...*/  swap(c,d);  // Instantiates a swap for char
      String s,t;  /*...*/  swap(s,t);  // Instantiates a swap for String
    } 
Nota: Uma função template é uma instanciação de uma template de função.
Topo
[ 31.6 ] Qual a sintaxe/semântica para uma template de classe?

Considere uma class Array cotainer que se comporta como uma matriz de inteiros:
    // This would go into a header file such as "Array.h"
    class Array {
    public:
      Array(int len=10)                  : len_(len), data_(new int[len]) { }
     ~Array()                            { delete [] data_; }
      int len() const                    { return len_;     }
      const int& operator[](int i) const { return data_[check(i)]; }
            int& operator[](int i)       { return data_[check(i)]; }
      Array(const Array&);
      Array& operator= (const Array&);
    private:
      int  len_;
      int* data_;
      int  check(int i) const
        { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
          return i; }
    }; 
Exatamente como no swap() anterior, repetir o código acima uma vêz para matriz de float, de char, de String, matriz-de-String, etc, seria tedioso.
    // This would go into a header file such as "Array.h"
    template<class T>
    class Array {
    public:
      Array(int len=10)                : len_(len), data_(new T[len]) { }
     ~Array()                          { delete [] data_; }
      int len() const                  { return len_;     }
      const T& operator[](int i) const { return data_[check(i)]; }
            T& operator[](int i)       { return data_[check(i)]; }
      Array(const Array<T>&);
      Array<T>& operator= (const Array<T>&);
    private:
      int len_;
      T*  data_;
      int check(int i) const
        { if (i < 0 || i >= len_) throw BoundsViol("Array", i, len_);
          return i; }
    }; 
Diferentemente das funções template, classes templates (instanciações de template de classe) precisam ser explícitas quanto aos parâmetros para os quais estão sendo instanciadas.
    main()
    {
      Array<int>           ai;
      Array<float>         af;
      Array<char*>         ac;
      Array<String>        as;
      Array< Array<int> >  aai;
    } 
Repare o espaço entre os dois sinais >'s no último exemplo. Sem esse espaço, o compilador interpretaria um >> (shift para a direita) ao invés de dois >'s
Topo
[ 31.7 ] O que é um tipo parametrizado?

Uma outra forma de dizer template de classe.

Um tipo parametrizado é um tipo que é parametrizado sobre outro tipo ou sobre algum valor. List<int> é um tipo (List) parametrizado sobre um outro tipo (int).

Topo
[ 31.8 ] O que é genericidade?

Uma outra forma de dizer template de classe.

Não confundir com generalidade, que significa, nesse contexto, soluções que não sejam específicas. Genericidade significa classes templates.

Topo Anterior Próximo Índice
C++ FAQ Lite
Copyright © 1991-98 by Marshall Cline Ph.D., cline@parashift.com
Tradução: Dagoberto Haele Arnaut

| Home | Bookmarks | Universidades | Para Saber mais | Universidades | WEB Directory | Mapa do site |