Access Violation

quinta-feira, novembro 10, 2005

Diferenças entre Generics X Templates C++
Neste post vou abordar as principais diferenças entre generics e templates (gostaria de reafirmar que o assunto é muito extenso e que não tenho a pretenção de abordá-lo em profundidade. Assim se você deseja aprender mais visite o site da Microsoft e/ou outros sites).

Assim, se você não conhece C++ pode ignorar este post. Também gostaria de lembrar que além dos pontos aqui abordados temos a questão da integração Generics X Templates quando desenvolvemos em Managed C++, mas isto é um outro assunto.

Bom, vamos começar pelas diferenças que vem melhorar a utilização de Generics em relação à templates: code bloating. Code Bloating é o efeito resultante da instanciação de um mesmo template para vários tipos de dados. Por exemplo, imagine o seguinte template em C++:

0: #include <stdio.h>
1:
2: template <typename T>
3: class MyTestTemplate
4: {
5: public:
6: MyTestTemplate(T t)
7: {
8: data_ = t;
9: }
10:
11: T GetIt()
12: {
13: return data_;
14: }
15:
16: private:
17: T data_;
18: };
19:
20: struct TestStruct
21: {
22: int n1;
23: char *str;
24: };
25:
26: void main()
27: {
28: MyTestTemplate<int> a(10); printf("int : %d\r\n", a.GetIt());
29: MyTestTemplate<char> b('A'); printf("char : %c\r\n", b.GetIt());
30: MyTestTemplate<double> c(1.2); printf("double: %f\r\n", c.GetIt());
31:
32: TestStruct t = {10, "Adriano"};
33: MyTestTemplate<TestStruct> d(t); printf("struct: %d %s\r\n", d.GetIt().n1, d.GetIt().str);
34: }

Uau! Isto não é nenhuma obra prima mas serve para nosso propósito! :).

No exemplo acima estamos instanciando o template MyTestTemplate 4 vezes (um para o tipo de dado int, um para char, um para double e um para TestStruct).

Neste exemplo o compilador irá gerar quatro cópias do código fonte, uma para cada tipo instanciado. De uma forma bastante simplista podemos dizer que o código fonte resultante é equivalente ao gerado se escrevessemos algo como:

0: #include <stdio.h>
1:
2: #define DEFINE_TEST_CLASS(TYPE) \
3: class MyTestTemplate_##TYPE \
4: { \
5: public: \
6: MyTestTemplate_##TYPE(TYPE t) \
7: { \
8: data_ = t; \
9: } \
10: \
11: TYPE GetIt() \
12: { \
13: return data_; \
14: } \
15: \
16: private: \
17: TYPE data_; \
18: }
19:
20: struct TestStruct
21: {
22: int n1;
23: char *str;
24: };
25:
26: DEFINE_TEST_CLASS(int);
27: DEFINE_TEST_CLASS(char);
28: DEFINE_TEST_CLASS(double);
29: DEFINE_TEST_CLASS(TestStruct);
30:
31: void main()
32: {
33: MyTestTemplate_int a(10); printf("int : %d\r\n", a.GetIt());
34: MyTestTemplate_char b('A'); printf("char : %c\r\n", b.GetIt());
35: MyTestTemplate_double c(1.2); printf("double: %f\r\n", c.GetIt());
36:
37: TestStruct t = {10, "Adriano"};
38: MyTestTemplate_TestStruct d(t); printf("struct: %d %s\r\n", d.GetIt().n1, d.GetIt().str);
39: }

Note que o processo de instanciação de templates não é simples assim (ou seja, não é apenas substituição de texto) mas para fins de análise de tamanho do código gerado podemos entender desta forma.

Agora ficou claro! Estamos criando 4 cópias do mesmo trecho de código.

Já com Generics este problema não existe quando utilizado com tipos referência (reference types) e é minimizado em muito quando uitilzados com tipos valores (value types). Para ser mais preciso, o compilador gera uma versão especializada do tipo generic para cada value type com o qual seja instanciado e apenas uma versão comum a todos os tipos referência utilizados. Assim, no exemplo abaixo (C#):

0: class MyTestTemplate<T>
1: {
2: public MyTestTemplate(T t)
3: {
4: data_ = t;
5: }
6:
7: public T GetIt()
8: {
9: return data_;
10: }
11:
12: private T data_;
13: }
14:
15: class Test {}
16: class Test_1 {}
17:
18: public class MyTestTemplateDriver
19: {
20: static void Main()
21: {
22: MyTestTemplate<int> a = new MyTestTemplate<int>(10);
23: MyTestTemplate<double> b = new MyTestTemplate<double>(20.0);
24: MyTestTemplate<Test> c = new MyTestTemplate<Test>(new Test()) ;
25: MyTestTemplate<Test_1> d = new MyTestTemplate<Test_1>(new Test_1()) ;
26: }
27: }

o compilador irá gerar três versões da classe MyTestTemplate() (uma para int, uma para double e uma para tipos referência) ao invés de quatro. Quando aplicado a código de programas de verdade isto pode representar uma diminuição significativa no tamanho do mesmo.

As demais diferenças entre os dois conceitos podem ser resumidas como:

- Generics, proprositadamente, não apresentam o mesmo grau de flexibilidade/complexidade que templates em C++.

Por exemplo, não é possível utilizar um type parameter como uma classe base do tipo genérico, ou seja, o exemplo abaixo não é válido:

0: class TypeParameterAsBaseClass<T> : T
1: {
2: }


- Generics não suporta type parameters que não sejam tipos

Por exemplo a classe abaixo não é válida em C#:

0: class NonType_TypeParameter<int i>
1: {
2: public static void Main()
3: {
4: NonType_TypeParameter<100> t = new NonType_TypeParameter<100>();
5: t.PrintIt();
6: }
7:
8: public void PrintIt()
9: {
10: System.Console.WriteLine("{0}", i);
11: }
12: }


Além destas diferenças, podemos citar também a falta de suporte a especialização (total/parcial) do tipos genéricos. Basicamente estas são as principais diferenças entre templates em C++ e generics no .Net.

Este post encerra nossa discussão sobre generics!

Até a próxima :)

[+/-] mostrar/esconder este post