logo
Contato | Sobre...        
rebarba rebarba

Rodrigo Strauss :: Blog


Tutorial de STL, parte 1/2: Mais templates

No primeiro post da série vimos qual a função básica dos templates: transformar os tipos de dados em parâmetros, assim como já são os valores. Isso permite que você faça uma função - na realidade um template de função - que consiga tratar vários tipos de dados e continuar tipada. Essa é a base do "generic programming", separar o algoritmo do tipo de dado. Dessa forma, qualquer tipo de dado que tenha suporte aos requisitos exigidos para o funcionamento do algoritmo pode ser usado. Esses requisitos em relação a um tipo de dado tem o nome de "concept", e será nativamente suportado pela linguagem no C++0x. Veremos mais sobre isso em breve.

No nosso primeiro exemplo resolvemos o problema de duplicação do código da função, mas não resolvemos o problema do código que chama a função, pois só trocamos a sintaxe soma_int para sintaxe soma<int>. O que eu não contei na primeira parte é que o compilador C++ faz a resolução automática de tipos para os parâmetros, sempre que possível. Nosso código pode ser modificado para:

template <typename T>
T soma(T x, T y)
{
  return x + y;
}

int main()
{
  soma(2,2);
  soma(2.99f, 2.99f);
  soma(5.9999, 6.9999);
  soma('2', '2'); // não, o resultado não será '4'...

  return 0;
}

Nesse caso específico, o compilador consegue saber com certeza qual o tipo de dado envolvido nas chamadas. Dessa forma podemos deixar o trabalho por conta dele. Mas existem alguns casos que não são claros:

  int i = 10;
  float f = 10.2;

  double d = soma(i,f);

------ Build started: Project: 1bit_stl, Configuration: Debug Win32 ------

Compiling...
1bit_stl.cpp
1bit_stl.cpp(115) : error C2782: 'T soma(T,T)' : template parameter 'T' is ambiguous
        1bit_stl.cpp(96) : see declaration of 'soma'
        could be 'float'
        or       'int'

Oops. Sabemos que o compilador poderia ser bonzinho e promover os dois parâmetros para double, fazer a conta, e retornar o resultado. Sim, mas lembre-se da filosofia C++: você só paga por aquilo que você pedir. Nos primeiros casos o compilador fez a dedução porque era absolutamente óbvio, nesse caso não é. Conversões implícitas podem ser uma praga a te perseguir, e o compilador C++ te livra disso exigindo que você diga exatamente o que você quer.

Outra coisa que podemos notar nesse exemplo: não existe sobrecarga de retorno de função. O fato de colocarmos o retorno da função em uma variável double não mudou em nada o comportamento da resolução de tipo. Em C++ você não pode fazer sobrecarga de função mudando somente o tipo de retorno. E esse nosso template nada mais é do que uma função vitaminada, as regras continuam valendo. Como nesse caso precisamos ser explícitos, vale a sintaxe normal:

  int i = 10;
  float f = 10.2;

  double d = soma<double>(i,f);

Dessa forma o compilador faz a promoção automática dos tipos int e float para double, o que não nos causa nenhum problema de perda de dados.

No começo do post eu falei sobre abstrair o algoritmo do tipo de dados, e é o que fizemos com nossa função soma. O conceito é simples: independente do tipo do dado, os passos necessários para se fazer uma soma são os mesmos. O que muda é a forma como a soma que é feita, que é uma característica intrínseca do tipo. Esse mesmo conceito se aplica para algoritmos mais complicados, como os de ordenação. Eles comparam os valores entre si, e a comparação de valores é diferente para cada tipo de dado. Mesmo assim, o algoritmo de ordenação continua o mesmo.

Um exemplo clássico que vou usar para ilustrar isso é uma classe para representar um número complexo. Um número complexo é representado com ai + b, sendo a e b dois números reais, e i a parte imaginária (sendo i ao quadrado igual a -1 e tendo seus detalhes explicados na Wikipedia como de costume). As operações de soma e subtração de um número complexo são feitas da seguinte forma:

Esses algoritmos acima não se encaixam com o nosso template de soma:

  complex_number c1  = {10, 20}, c2  = {40, 30};
  soma<complex_number>(c1, c2);

------ Build started: Project: 1bit_stl, Configuration: Debug Win32 ------

Compiling...
1bit_stl.cpp
1bit_stl.cpp(98) : error C2676: binary '+' : 'complex_number' does not define this operator 
  or a conversion to a type acceptable to the predefined operator
        1bit_stl.cpp(128) : see reference to function template 
  instantiation 'T soma(T,T)' being compiled
        with
        [
            T=complex_number
        ]

Build Time 0:00
1bit_stl - 1 error(s), 0 warning(s)

---------------------- Done ----------------------

    Build: 0 succeeded, 1 failed, 0 skipped

Note que os passos para nosso complicadíssimo algoritmo ainda são os mesmos - passo único: efetuar a operação de soma - o que muda é forma como a soma é efetuada. Para ficar mais claro, imagine uma equação quadrada:

Nesse caso a soma é só um passo do algortimo para se chegar ao resultado. A forma como a soma é feita é diferente para números inteiros (int) ou reais (double), mas a ordem dessa soma dentro dos passos do algoritmo ainda é a mesma.

Voltando ao nosso super algoritmo de soma, precisamos de um tratamento diferenciado para somar números complexos. Veremos as várias formas de resolver isso no próximo post.


Em 30/05/2006 16:06, por Rodrigo Strauss


  
 
 
Comentários
Angel Portal | website | em 18/07/2006 | #
Gostaria de saber se tem este tutorial para download em algum
lugar?
Tambem gostaria de saber se ver com os compiladores normais como o Borland C++ ou se tenho que instalar no meu ambiente?

Angel Portal
Rodrigo Strauss | website | em 18/07/2006 | #
Como assim download? Esse é o tutorial.

Não entendi sua pergunta sobre o Borland C++
diego | em 04/06/2007 | #
cara este artigo teu me ajudou de mais
valeu
Midori | website | em 13/08/2008 | #
uhuu
to começando a estudar C++ o/
Rodrigo Strauss | website | em 15/08/2008 | #
:-)
Insaulo | em 18/07/2011 | #
Pô cara, muito bom o blog, mas cadê as atulizações?! Você abandonou o site, parecia um bom lugar pra aprender c++...
rebarba rebarba
  ::::