Introdução: o que são Generics em C++ e para que servem?
No mundo da programação, surge frequentemente a tarefa de escrever código que funcione igualmente bem com diferentes tipos de dados. Por exemplo, uma função de ordenação de array deve ser capaz de ordenar tanto números inteiros quanto strings ou objetos personalizados. Sem o mecanismo de programação genérica (generics), seria necessário escrever funções separadas para cada tipo, o que leva à duplicação de código, erros e complexidade de manutenção.
Em C++, este problema é resolvido com templates — uma ferramenta poderosa que permite criar funções e classes genéricas. O conceito de generics em C++ é implementado precisamente através de templates. Eles permitem que o programador defina um algoritmo ou estrutura de dados sem se prender a um tipo específico, substituindo o tipo em tempo de compilação.
Principais vantagens do uso de templates:
- Redução da duplicação de código — um template substitui múltiplas funções sobrecarregadas.
- Segurança de tipos — todas as verificações de tipo são realizadas em tempo de compilação.
- Desempenho — templates não adicionam overhead em tempo de execução, pois o código é gerado para cada tipo específico separadamente.
- Flexibilidade — é possível criar construções genéricas complexas, incluindo contêineres (STL) e algoritmos.
Neste artigo, vamos detalhar como usar templates em C++, começando com exemplos simples e terminando com técnicas avançadas.
Fundamentos do trabalho com templates de funções
A maneira mais simples de conhecer generics é escrever uma função template. Suponha que precisamos de uma função que encontre o máximo entre dois valores. Sem templates, seria necessário escrever sobrecargas para int, double, std::string, etc. Com templates, isso é feito uma vez:
#include <iostream>#include <string>
// Template de funçãotemplate <typename T>T max(T a, T b) { return (a > b) ? a : b;}
int main() { std::cout << max(10, 20) << std::endl; // int std::cout << max(3.14, 2.71) << std::endl; // double std::cout << max("apple", "orange") << std::endl; // const char* (comparação de strings) return 0;}Aqui, a palavra-chave template declara um template, e typename T (ou class T) define o parâmetro de tipo. O compilador gera automaticamente uma versão da função para cada tipo utilizado. É importante notar que os tipos devem suportar o operador > — caso contrário, ocorrerá um erro de compilação.
Também é possível especificar vários parâmetros de tipo:
template <typename T1, typename T2>void printPair(T1 a, T2 b) { std::cout << "(" << a << ", " << b << ")" << std::endl;}
// UsoprintPair(42, "Hello"); // (42, Hello)Templates de classes: criação de contêineres universais
Templates de classes permitem criar estruturas de dados genéricas — esta é a base da Standard Template Library (STL). Vamos considerar um exemplo simples de uma classe template para um array dinâmico:
#include <iostream>#include <stdexcept>
template <typename T>class Array {private: T* data; size_t size;public: Array(size_t sz) : size(sz) { data = new T[size]; } ~Array() { delete[] data; } T& operator[](size_t index) { if (index >= size) { throw std::out_of_range("Index out of range"); } return data[index]; } size_t getSize() const { return size; }};
int main() { Array<int> intArray(5); // Array de inteiros Array<double> doubleArray(3); // Array de números de ponto flutuante return 0;}