Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Os modelos são a base para a programação genérica em C++. Como uma linguagem fortemente tipada, C++ requer que todas as variáveis tenham um tipo específico, explicitamente declarado pelo programador ou deduzido pelo compilador. No entanto, muitas estruturas de dados e algoritmos têm a mesma aparência, independentemente do tipo em que estão operando. Os modelos permitem definir as operações de uma classe ou função e permitem que o usuário especifique em quais tipos concretos essas operações devem funcionar.
Definição e utilização de modelos
Um modelo é uma construção que gera um tipo ou função comum em tempo de compilação com base nos argumentos que o usuário fornece para os parâmetros do modelo. Por exemplo, você pode definir um modelo de função como este:
template <typename T>
T minimum(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}
O código acima descreve um modelo para uma função genérica com um único parâmetro de tipo T, cujo valor de retorno e parâmetros de chamada (lhs e rhs) são todos desse tipo. Você pode nomear um parâmetro de tipo como quiser, mas, por convenção, letras maiúsculas únicas são mais comumente usadas.
T é um parâmetro de modelo; A typename palavra-chave diz que esse parâmetro é um espaço reservado para um tipo. Quando a função é chamada, o compilador substituirá cada instância de com o argumento de T tipo concreto que é especificado pelo usuário ou deduzido pelo compilador. O processo no qual o compilador gera uma classe ou função a partir de um modelo é referido como instanciação de modelo; minimum<int> é uma instanciação do modelo minimum<T>.
Em outro lugar, um usuário pode declarar uma instância do modelo que é especializada para int. Suponha que get_a() e get_b() são funções que retornam um int:
int a = get_a();
int b = get_b();
int i = minimum<int>(a, b);
No entanto, como este é um modelo de função e o compilador pode deduzir o tipo dos argumentos T e b, você pode chamá-lo como uma função comum:
int i = minimum(a, b);
Quando o compilador encontra essa última instrução, ele gera uma nova função na qual cada ocorrência de T no modelo é substituída por int:
int minimum(const int& lhs, const int& rhs)
{
return lhs < rhs ? lhs : rhs;
}
As regras para como o compilador executa a dedução de tipo em modelos de função são baseadas nas regras para funções comuns. Para obter mais informações, consulte Resolução de sobrecarga de chamadas de modelo de função.
Parâmetros de tipo
minimum No modelo acima, observe que o parâmetro de tipo T não é qualificado de forma alguma até que seja usado nos parâmetros de chamada de função, onde os qualificadores const e reference são adicionados.
Não há limite prático para o número de parâmetros de tipo. Separe vários parâmetros por vírgulas:
template <typename T, typename U, typename V> class Foo{};
A palavra-chave class é equivalente a typename neste contexto. Você pode expressar o exemplo anterior como:
template <class T, class U, class V> class Foo{};
Você pode usar o operador de reticências (...) para definir um modelo que usa um número arbitrário de zero ou mais parâmetros de tipo:
template<typename... Arguments> class vtclass;
vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
Qualquer tipo interno ou definido pelo usuário pode ser usado como um argumento de tipo. Por exemplo, você pode usar std::vetor na Biblioteca Padrão para armazenar variáveis do tipo int, double, std::string, MyClass, constMyClass*, MyClass&e assim por diante. A principal restrição ao usar modelos é que um argumento type deve suportar quaisquer operações que são aplicadas aos parâmetros de tipo. Por exemplo, se chamarmos minimum usando MyClass como neste exemplo:
class MyClass
{
public:
int num;
std::wstring description;
};
int main()
{
MyClass mc1 {1, L"hello"};
MyClass mc2 {2, L"goodbye"};
auto result = minimum(mc1, mc2); // Error! C2678
}
Um erro de compilador será gerado porque MyClass não fornece uma sobrecarga para o < operador.
Não há nenhum requisito inerente de que os argumentos de tipo para qualquer modelo específico pertençam todos à mesma hierarquia de objetos, embora você possa definir um modelo que imponha tal restrição. Você pode combinar técnicas orientadas a objetos com modelos; por exemplo, você pode armazenar um Derived* em uma Base vetorial<*>. Observe que os argumentos devem ser ponteiros
vector<MyClass*> vec;
MyDerived d(3, L"back again", time(0));
vec.push_back(&d);
// or more realistically:
vector<shared_ptr<MyClass>> vec2;
vec2.push_back(make_shared<MyDerived>());
Os requisitos básicos que std::vector e outros contêineres de biblioteca padrão impõem aos elementos de é que T sejam passíveis de cópia e de construção de T cópia.
Parâmetros não tipográficos
Ao contrário dos tipos genéricos em outras linguagens, como C# e Java, os modelos C++ suportam parâmetros não-tipo, também chamados de parâmetros de valor. Por exemplo, você pode fornecer um valor integral constante para especificar o comprimento de uma matriz, como neste exemplo que é semelhante à classe std::array na Biblioteca Padrão:
template<typename T, size_t L>
class MyArray
{
T arr[L];
public:
MyArray() { ... }
};
Observe a sintaxe na declaração do modelo. O size_t valor é passado como um argumento de modelo em tempo de compilação e deve ser const ou uma constexpr expressão. Você o usa assim:
MyArray<MyClass*, 10> arr;
Outros tipos de valores, incluindo ponteiros e referências, podem ser passados como parâmetros não tipo. Por exemplo, você pode passar um ponteiro para uma função ou objeto de função para personalizar alguma operação dentro do código do modelo.
Dedução de tipo para parâmetros de modelo não tipo
No Visual Studio 2017 e posterior, e no /std:c++17 modo ou posterior, o compilador deduz o tipo de um argumento de modelo não-tipo que é declarado com auto:
template <auto x> constexpr auto constant = x;
auto v1 = constant<5>; // v1 == 5, decltype(v1) is int
auto v2 = constant<true>; // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>; // v3 == 'a', decltype(v3) is char
Modelos como parâmetros de modelo
Um modelo pode ser um parâmetro de modelo. Neste exemplo, MyClass2 tem dois parâmetros de modelo: um parâmetro typename T e um parâmetro de modelo Arr:
template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
T t; //OK
Arr<T, 10> a;
U u; //Error. U not in scope
};
Como o parâmetro Arr em si não tem corpo, seus nomes de parâmetro não são necessários. Na verdade, é um erro referir-se ao nome de tipo ou aos nomes de parâmetros de classe de Arr a partir do corpo de MyClass2. Por esse motivo, os nomes dos parâmetros de tipo de Arr podem ser omitidos, como mostrado neste exemplo:
template<typename T, template<typename, int> class Arr>
class MyClass2
{
T t; //OK
Arr<T, 10> a;
};
Argumentos de modelo padrão
Os modelos de classe e função podem ter argumentos padrão. Quando um modelo tem um argumento padrão, você pode deixá-lo não especificado quando usá-lo. Por exemplo, o modelo std::vetor tem um argumento padrão para o alocador:
template <class T, class Allocator = allocator<T>> class vector;
Na maioria dos casos, a classe padrão std::allocator é aceitável, então você usa um vetor como este:
vector<int> myInts;
Mas, se necessário, você pode especificar um alocador personalizado como este:
vector<int, MyAllocator> ints;
Para vários argumentos de modelo, todos os argumentos após o primeiro argumento padrão devem ter argumentos padrão.
Ao usar um modelo cujos parâmetros são todos padrão, use colchetes angulares vazios:
template<typename A = int, typename B = double>
class Bar
{
//...
};
...
int main()
{
Bar<> bar; // use all default type arguments
}
Especialização de modelos
Em alguns casos, não é possível ou desejável que um modelo defina exatamente o mesmo código para qualquer tipo. Por exemplo, você pode querer definir um caminho de código a ser executado somente se o argumento type for um ponteiro, ou um std::wstring, ou um tipo derivado de uma classe base específica. Nesses casos, você pode definir uma especialização do modelo para esse tipo específico. Quando um usuário instancia o modelo com esse tipo, o compilador usa a especialização para gerar a classe e, para todos os outros tipos, o compilador escolhe o modelo mais geral. Especializações em que todos os parâmetros são especializados são especializações completas. Se apenas alguns dos parâmetros são especializados, é chamado de especialização parcial.
template <typename K, typename V>
class MyMap{/*...*/};
// partial specialization for string keys
template<typename V>
class MyMap<string, V> {/*...*/};
...
MyMap<int, MyClass> classes; // uses original template
MyMap<string, MyClass> classes2; // uses the partial specialization
Um modelo pode ter qualquer número de especializações, desde que cada parâmetro de tipo especializado seja exclusivo. Apenas os modelos de classe podem ser parcialmente especializados. Todas as especializações completas e parciais de um modelo devem ser declaradas no mesmo namespace que o modelo original.
Para obter mais informações, consulte Especialização de modelo.