Isenção de responsabilidade: o artigo foi iniciado em fevereiro, mas, por motivos que dependem de mim, não foi concluído. O tópico é muito extenso e, portanto, é publicado de forma truncada. O que não couber será considerado mais tarde.

É impossível entender o C ++ moderno sem saber o que são padrões de programação. Essa propriedade do idioma abre amplas possibilidades para otimizar e reutilizar o código. Neste artigo, tentaremos descobrir o que é e como tudo funciona.
O mecanismo de modelos em C ++ nos permite resolver o problema de unificar o algoritmo para vários tipos: não há necessidade de escrever funções diferentes para tipos inteiros, reais ou de usuário - basta compilar um algoritmo generalizado que não depende do tipo de dados, com base apenas em propriedades comuns. Por exemplo, o algoritmo de classificação pode funcionar com números inteiros e com objetos do tipo "car".
Existem modelos de função e modelos de classe.
Modelos de função são uma descrição geral do comportamento de funções que podem ser chamadas para objetos de diferentes tipos. Em outras palavras, um modelo de função (função de modelo, função generalizada) é uma família de funções diferentes (ou uma descrição de algoritmo). Por descrição, o modelo de função é semelhante a uma função regular: a diferença é que alguns elementos não são definidos (tipos, constantes) e são parametrizados.
Modelos de classe - uma descrição generalizada de um tipo de usuário no qual atributos e operações de tipo podem ser parametrizados. São construções pelas quais classes reais podem ser geradas substituindo argumentos concretos em vez de parâmetros.
Vamos considerar os modelos de função em mais detalhes.
Modelos de Função
Como escrever a primeira função de modelo?
Considere o caso de determinar o elemento mínimo de dois. No caso de números inteiros e números reais, você terá que escrever 2 funções.
int _min(int a, int b){ if( a < b){ return a; } return b; } double _min(double a, double b){ if( a < b){ return a; } return b; }
Obviamente, você pode implementar apenas uma função, com parâmetros válidos, mas, para entender os padrões, isso será prejudicial.
O que acontece se o aplicativo for compilado? Ambas as implementações da função caem no código binário do aplicativo, mesmo que não sejam usadas (no entanto, agora os compiladores são muito inteligentes, podem cortar o código não utilizado). E se você precisar adicionar uma função que defina o mínimo de 2 linhas (é difícil imaginar sem especificar que há uma linha mínima) ?!
Nesse caso, se o algoritmo for comum para os tipos com os quais você precisa trabalhar, você pode definir um modelo de função. O princípio, no caso geral, será o seguinte:
- uma implementação de função é usada para algum tipo;
- o modelo de cabeçalho <tipo de classe> (ou modelo <tipo de nome de tipo>) é atribuído, o que significa que o algoritmo usa algum tipo de tipo abstrato tipo;
- na implementação da função, o nome do tipo é substituído por Type.
Para a função min, obtemos o seguinte:
template<class Type> Type _min(Type a, Type b){ if( a < b){ return a; } return b; }
O mais interessante é o fato de que, embora não haja chamada para a função min, quando compilada, ela não é criada em código binário (não
instanciado ). E se você declarar um grupo de chamadas de função com variáveis de vários tipos, para cada compilador criará sua própria implementação com base no modelo.
Chamar uma função de modelo é geralmente equivalente a chamar uma função comum. Nesse caso, o compilador determinará qual tipo usar em vez de Type, com base no tipo dos parâmetros reais. Mas se os parâmetros substituídos forem de tipos diferentes, o compilador não poderá produzir (instanciar o modelo) a implementação do modelo. Portanto, no código a seguir, o compilador tropeçará na terceira chamada, porque não pode determinar o que é Type (pense em por quê?):
#include <iostream> template<class Type> Type _min(Type a, Type b) { if (a < b) { return a; } return b; } int main(int argc, char** argv) { std::cout << _min(1, 2) << std::endl; std::cout << _min(3.1, 1.2) << std::endl; std::cout << _min(5, 2.1) << std::endl; // oops! return 0; }
Esse problema é resolvido especificando um tipo específico ao chamar a função.
#include <iostream> template<class Type> Type _min(Type a, Type b) { if (a < b) { return a; } return b; } int main(int argc, char** argv) { std::cout << _min<double>(5, 2.1) << std::endl; return 0; }
Quando o modelo funcionará (não)?
Em princípio, você pode entender que o compilador simplesmente substitui o tipo desejado no modelo. Mas a função resultante sempre será funcional? Obviamente não. Qualquer algoritmo pode ser definido independentemente do tipo de dados, mas necessariamente usa as propriedades desses dados. No caso da função de modelo _min, este é um requisito para definir um operador de pedidos (operador <).
Qualquer modelo de função assume a presença de certas propriedades de um tipo parametrizado, dependendo da implementação (por exemplo, um operador de cópia, um operador de comparação, a presença de um método específico etc.). No padrão esperado da linguagem C ++, os conceitos serão responsáveis por isso.Sobrecarga de modelo de função
Os modelos de função também podem ser sobrecarregados. Geralmente essa sobrecarga é realizada quando
template<class Type> Type* _min(Type* a, Type* b){ if(*a < *b){ return a; } return b; }
Casos especiais
Em alguns casos, o modelo de função é ineficiente ou incorreto para um tipo específico. Nesse caso, você pode
especializar o modelo, ou seja, escrever uma implementação para esse tipo. Por exemplo, no caso de cadeias, você pode querer que a função compare apenas o número de caracteres. No caso de especialização do modelo de função, o tipo para o qual o modelo é especificado não é especificado no parâmetro A seguir, é apresentado um exemplo dessa especialização.
template<> std::string _min(std::string a, std::string b){ if(a.size() < b.size()){ return a; } return b; }
A especialização do modelo para tipos específicos é feita novamente por razões de economia: se esta versão do modelo de função não for usada no código, ela não será incluída no código binário.
Para o futuro, parâmetros múltiplos e inteiros permanecem. Uma extensão natural são os modelos de classe, os conceitos básicos de programação generativa e o design da biblioteca padrão C ++. E um monte de exemplos!