Explicação detalhada dos modelos elementares de C++

insira a descrição da imagem aqui

modelo não tipo

insira a descrição da imagem aqui

Non-type template (Non-Type Template) é uma forma de template em C++, que permite passar outros valores além dos tipos no template, como inteiros, enumerações, ponteiros, etc. Esses parâmetros podem ser analisados ​​em tempo de compilação e usados ​​para gerar versões instanciadas de modelos.

Um parâmetro de modelo sem tipo (parâmetro de modelo sem tipo) está na declaração do modelo, como parte do parâmetro, não parte do tipo. Eles podem ser expressões constantes, como constantes inteiras, enumerações, ponteiros, referências, etc. Os valores dos parâmetros do modelo não tipo devem ser conhecidos em tempo de compilação, pois são usados ​​para gerar uma instância específica do modelo.

Parâmetros de modelo classificam parâmetros de tipo para parâmetros não-tipo.

Parâmetro de tipo : aparece em uma lista de parâmetros de modelo, seguido por um nome de tipo de parâmetro, como ou class. Parâmetro não tipo : É para usar uma constante como parâmetro de um modelo de classe (função), e o parâmetro pode ser usado como uma constante no modelo de classe (função). Um exemplo típico de uso de parâmetros de modelo não tipo é implementar um contêiner de matriz de tamanho fixo, em que o tamanho da matriz é especificado pelo parâmetro de modelo em tempo de compilação.typename

Por exemplo, aqui está um exemplo usando um parâmetro de modelo não tipo:

template <typename T, int Size>
class FixedSizeArray {
    
    
private:
    T data[Size];
public:
    // ...
};

// 实例化一个大小为 10 的 FixedSizeArray,存储 int 类型
FixedSizeArray<int, 10> intArray;

Nesse caso, int Sizeé um parâmetro de modelo não tipo que determina FixedSizeArrayo tamanho da matriz de . No tempo de compilação, intArrayo tamanho de será determinado como o parâmetro passado para 10o tempo de instanciação .10Size

Perceber:

  1. Flutuantes, objetos de classe e strings não são permitidos como parâmetros de modelo não tipo
  2. Parâmetros de modelo não tipo devem ter um resultado confirmado em tempo de compilação.

especialização em modelo

Especialização de modelo ( Especialização de modelo ) é C++um mecanismo que permite fornecer implementações personalizadas para tipos específicos ou condições específicas. A especialização de modelo permite fornecer uma implementação de modelo especializado que substitui o comportamento de um modelo genérico para determinados tipos ou condições.

Em modelos gerais, você pode fornecer uma implementação de modelo comum para diferentes tipos de dados. No entanto, em alguns casos, tipos específicos podem exigir tratamento especial ou comportamento personalizado. Neste ponto, você pode fornecer implementações especiais para esses tipos específicos por meio da especialização de modelo.

Existem dois tipos de especializações de modelo:

Especialização de modelo de classe: uma implementação especial para um tipo específico.
Function Template Specialization: Uma implementação especial para um determinado tipo.

Em alguns casos, a falha em usar uma especialização de um modelo pode fazer com que o compilador escolha a implementação errada, resultando em erros ambíguos ou comportamento inesperado. Uma situação comum é que a correspondência de parâmetros de modelos de função é ambígua e o compilador não pode determinar qual implementação de modelo deve ser usada. Nesse caso, a especialização pode fornecer informações explícitas para ajudar o compilador a escolher a implementação correta.

Aqui está um exemplo do que pode dar errado sem a especialização de modelo:

#include <iostream>

template <typename T>
void printType(T value) {
    
    
    std::cout << "Generic template" << std::endl;
}

// 模板特化版本,针对 int 类型
template <>
void printType<int>(int value) {
    
    
    std::cout << "Specialized template for int" << std::endl;
}

int main() {
    
    
    int num = 5;
    printType(num);  // 编译错误,不明确的调用

    return 0;
}

No exemplo acima, sem especialização de template, o compilador não pode determinar qual implementação de template deve ser chamada, porque a printTypefunção pode aceitar argumentos de qualquer tipo. Isso resulta em erros de compilação porque o compilador não pode escolher a implementação correta com base no contexto.

Ao printType<int>fornecer uma especialização para , informamos explicitamente ao compilador intqual implementação ele deve usar ao lidar com parâmetros de tipo. Nesse caso, a especialização pode resolver o problema de chamadas ambíguas e garantir que o compilador escolha a implementação correta.

1. Especialização em modelo de função

As etapas de especialização de um modelo de função:

  1. Deve haver um modelo de função básica primeiro
  2. Uma palavra-chave templateé seguida por um par de colchetes angulares vazios<>
  3. O nome da função é seguido por um par de colchetes angulares, que especificam o tipo a ser especializado
  4. Tabela de parâmetros da função: Deve ser exatamente igual ao tipo de parâmetro básico da função do modelo.Se for diferente, o compilador pode relatar alguns erros estranhos.

Exemplo de especialização de modelo de função:

#include <iostream>

// 通用的函数模板
template <typename T>
T add(T a, T b) {
    
    
    return a + b;
}

// 函数模板的特化版本,针对 int 类型
template <>
int add(int a, int b) {
    
    
    std::cout << "Specialized version for int" << std::endl;
    return a + b + 10;
}

int main() {
    
    
    int result1 = add(5, 3);    // 调用通用版本
    std::cout << "Result 1: " << result1 << std::endl;  // 输出: 8

    int result2 = add<int>(5, 3);  // 调用特化版本
    std::cout << "Result 2: " << result2 << std::endl;  // 输出: Specialized version for int
                                                        // Result 2: 18

    return 0;
}

No exemplo acima, addé um modelo de função para calcular a soma de dois números. Ao add<int>fornecer uma especialização para , substituímos o comportamento padrão e o personalizamos para um tipo específico. Na mainfunção, mostramos como chamar as versões genérica e especializada e sua saída.

Nota :Em geral, se um modelo de função encontra um tipo que não pode ser manipulado ou é manipulado incorretamente, a função geralmente é fornecida diretamente por uma questão de simplicidade.

2. Especialização de modelo de classe

Em C++, uma especialização de modelo de classe refere-se ao fornecimento de uma implementação personalizada de um modelo de classe para um determinado tipo ou condição. Existem dois tipos de especialização de modelo de classe: Especialização completa e Especialização parcial.

Full Specialization : Uma especialização completa é uma especialização completa para um tipo particular. Quando você fornece uma especialização completa para um determinado tipo, ela substitui a definição de modelo de classe genérica.A sintaxe para especialização completa é adicionar após o nome do modelo <>e especificar o tipo de especialização.

Exemplo:

// 通用的类模板
template <typename T>
class MyTemplate {
    
    
public:
    void print() {
    
    
        std::cout << "Generic Template" << std::endl;
    }
};

// 类模板的全特化版本,针对 int 类型
template <>
class MyTemplate<int> {
    
    
public:
    void print() {
    
    
        std::cout << "Specialized Template for int" << std::endl;
    }
};

Especialização Parcial : A especialização parcial é a especialização de parâmetros de modelo sob certas condições, geralmente para personalização mais refinada. A especialização parcial permite fornecer especializações para determinados casos, em vez de especializações completas para cada tipo.A sintaxe para especialização parcial é adicionar após o nome do modelo <>e especificar os parâmetros a serem especializados entre colchetes angulares.

Exemplo:

// 通用的类模板
template <typename T, typename U>
class Pair {
    
    
public:
    Pair(T first, U second) : first_(first), second_(second) {
    
    }
    void print() {
    
    
        std::cout << "Generic Pair: " << first_ << ", " << second_ << std::endl;
    }
private:
    T first_;
    U second_;
};

// 类模板的偏特化版本,针对两个相同类型的参数
template <typename T>
class Pair<T, T> {
    
    
public:
    Pair(T first, T second) : first_(first), second_(second) {
    
    }
    void print() {
    
    
        std::cout << "Specialized Pair for same type: " << first_ << ", " << second_ << std::endl;
    }
private:
    T first_;
    T second_;
};

No exemplo acima, uma especialização completa fornece uma especialização para um tipo integral, enquanto uma especialização parcial fornece uma especialização para dois parâmetros do mesmo tipo.

Resumir:

Especialização completa : fornece uma especialização completa para um tipo específico.
Especialização parcial : Forneça versões especializadas para casos ou condições específicas.

Ao usar a especialização de modelo, você pode fornecer uma implementação precisa para um modelo de classe nos casos em que é necessário um comportamento personalizado, aumentando a flexibilidade e a aplicabilidade do modelo.

Os modelos de classe nos permitem fornecer a mesma estrutura de código para diferentes tipos de dados para acomodar vários tipos de necessidades. No entanto, em alguns casos, o compilador pode não ser capaz de deduzir como comparar objetos de tipos diferentes. É por isso que, no exemplo abaixo, o compilador pode não classificar a classe Date corretamente sem usar uma especialização do modelo de classe.

Por padrão, std::sorta função usa <operadores elementares para comparar os tamanhos dos elementos. Isso não é problema para tipos primitivos, como inteiros, ou < tipos que oferecem suporte a operadores. masPara tipos personalizados (como a classe Date), o compilador não tem como saber como realizar a comparação.

Ao usar especializações de modelos de classe, fornecemos lógica de comparação explícita para diferentes tipos de classes de data, ou seja, operator<sobrecarga de operador. Isso informa ao compilador como comparar objetos Date em determinadas circunstâncias, para que std::sorta função funcione corretamente.

Os modelos de classe especializados nos permitem fornecer implementações personalizadas para tipos específicos quando necessário, resolvendo assim problemas que o compilador não pode inferir e garantindo que o programa possa ser executado corretamente.

Aqui está um exemplo na classe de data, usando a especialização de modelo de classe para comparar o tamanho e std::sortclassificar as datas por meio da função .
No exemplo, definimos um Datemodelo de classe e Datefornecemos especializações para a classe para implementar comparações de datas:

#include <iostream>
#include <vector>
#include <algorithm>

// 通用的类模板
template <typename T>
class Date {
    
    
public:
    Date(T year, T month, T day) : year_(year), month_(month), day_(day) {
    
    }

    // 比较运算符
    bool operator<(const Date& other) const {
    
    
        if (year_ != other.year_) return year_ < other.year_;
        if (month_ != other.month_) return month_ < other.month_;
        return day_ < other.day_;
    }

    void print() {
    
    
        std::cout << year_ << "-" << month_ << "-" << day_ << std::endl;
    }

private:
    T year_;
    T month_;
    T day_;
};

// 类模板的特化版本,用于 int 类型
template <>
class Date<int> {
    
    
public:
    Date(int year, int month, int day) : year_(year), month_(month), day_(day) {
    
    }

    bool operator<(const Date<int>& other) const {
    
    
        if (year_ != other.year_) return year_ < other.year_;
        if (month_ != other.month_) return month_ < other.month_;
        return day_ < other.day_;
    }

    void print() {
    
    
        std::cout << year_ << "-" << month_ << "-" << day_ << std::endl;
    }

private:
    int year_;
    int month_;
    int day_;
};

int main() {
    
    
    std::vector<Date<int>> dates = {
    
    
        {
    
    2022, 8, 15},
        {
    
    2021, 12, 25},
        {
    
    2022, 1, 1},
        {
    
    2022, 3, 20}
    };

    std::cout << "Before sorting:" << std::endl;
    for (const auto& date : dates) {
    
    
        date.print();
    }

    std::sort(dates.begin(), dates.end());

    std::cout << "After sorting:" << std::endl;
    for (const auto& date : dates) {
    
    
        date.print();
    }

    return 0;
}

No exemplo acima, primeiro definimos um Datemodelo de classe genérico para armazenar informações de ano, mês e dia. Em seguida, Date<int>fornecemos especializações para implementar intoperações de comparação de datas com base no tipo.

Na mainfunção, criamos uma matriz para armazenar as datas std::vectore, em seguida, usamos std::sorta função para classificar as datas. Como Date<int>fornecemos uma especialização para , ele compara datas e as classifica corretamente. Por fim, exibimos a ordem de data antes e depois da classificação, respectivamente.

Compilação separada do modelo

1. O que é compilação separada

Compilação Separada ( Separated Compilation ) é uma técnica de desenvolvimento de software que divide um grande programa em vários pequenos arquivos de código-fonte, e cada arquivo contém a definição e implementação de uma ou mais funções, classes ou variáveis ​​relacionadas. Esses arquivos de código-fonte podem ser compilados em diferentes unidades de compilação e, em seguida, combinados em um programa executável durante o estágio de vinculação.

O principal objetivo da compilação separada é melhorar a capacidade de manutenção do código, a velocidade de compilação e a utilização de recursos. Aqui estão algumas vantagens da compilação separada:

Desenvolvimento modular : Divide o programa em vários módulos, cada módulo é responsável por uma função específica. Desta forma, diferentes desenvolvedores podem trabalhar em diferentes módulos de forma independente, melhorando assim a eficiência do desenvolvimento.
Reutilização de código : Em diferentes projetos, os módulos que já foram escritos e testados podem ser reutilizados, reduzindo assim o tempo de desenvolvimento e os recursos.
Velocidade de compilação : Apenas módulos modificados precisam ser recompilados, outros módulos não modificados podem permanecer inalterados. Isso pode acelerar significativamente os tempos de compilação.
Utilização de recursos : Somente os módulos necessários serão compilados, reduzindo a compilação desnecessária e o uso de memória.

O processo básico de compilação separada é o seguinte:

Módulos de escrita : Divida o programa em módulos e escreva a definição e implementação de cada módulo.
Compile Modules : Compile o código-fonte de cada módulo separadamente, produzindo arquivos objeto (por exemplo .obj, .o arquivos).
Módulo de link : Vincule todos os arquivos de objeto, resolva relacionamentos de referência e gere o arquivo executável final.

Na compilação separada, os arquivos de cabeçalho (.h 文件)geralmente são usados ​​para armazenar declarações de funções e classes, enquanto os arquivos de origem (.cpp 文件)contêm implementações de funções e classes. Essa divisão pode ajudar o compilador a entender a interface e a implementação de cada módulo, para que ele possa estabelecer o link correto entre os diferentes módulos.

A compilação separada é uma prática importante no desenvolvimento de software moderno, que ajuda a organizar projetos complexos, melhorar a eficiência do desenvolvimento e reduzir os custos de manutenção.

2. Compilação separada de modelos

Em C++, a compilação separada de modelos refere-se à colocação da declaração e implementação de modelos em arquivos diferentes . A declaração do modelo geralmente é colocada no arquivo de cabeçalho ( .hou .hpparquivo) e a implementação do modelo é colocada no arquivo de origem ( .cpparquivo).

A compilação separada de modelos é para resolver o problema de instanciação de modelo no momento do link . O compilador C++ precisa instanciar o modelo onde o modelo é usado, mas o compilador só pode ver o conteúdo do arquivo de origem atual ao compilar um arquivo de origem e não pode conhecer os detalhes de implementação do modelo em outros arquivos de origem. portanto,Se a declaração e a implementação do modelo forem colocadas no arquivo de cabeçalho e referenciadas por vários arquivos de origem, o modelo será instanciado várias vezes e, eventualmente, haverá várias instanciações idênticas na fase de vinculação, causando um erro de redefinição.

A prática geral de separar definições de compilação de modelos é:

Coloque a declaração do modelo em um arquivo de cabeçalho (por exemplo, .harquivo).
Coloque a implementação do modelo em um arquivo de origem (por exemplo, .cpparquivo) e inclua a implementação do modelo no final do arquivo de origem.

A vantagem disso é que cada arquivo de origem instanciará o modelo apenas uma vez, evitando problemas de redefinição.
No entanto, definições separadas de modelos também podem causar problemas, como:

Erros de compilação são difíceis de localizar : Se houver um erro na implementação do modelo, o compilador pode não ser capaz de fornecer informações detalhadas sobre o erro onde o modelo é usado, dificultando a depuração.
Dificuldade na manutenção do código : A implementação dos templates está espalhada em vários arquivos fonte, o que pode tornar a manutenção do código mais complicada, sendo necessário garantir que as implementações dos templates de cada arquivo fonte sejam consistentes.
Legibilidade reduzida : a implementação do modelo é separada no arquivo de origem, o que pode reduzir a legibilidade e a compreensão do código.

Para evitar os problemas causados ​​pela definição da separação do template, algumas práticas de programação recomendam que tanto a declaração quanto a implementação do template sejam colocadas no arquivo de cabeçalho, para que os detalhes completos da implementação possam ser vistos onde o template é usado. Se a implementação do modelo for mais complicada, o problema da definição separada pode ser resolvido especializando o modelo.

Dê um exemplo de definições separadas de modelos em C++

Este exemplo demonstra que podem ocorrer erros de redefinição se a declaração e a implementação de um modelo forem separadas em arquivos diferentes.

Suponha que temos os dois arquivos a seguir:

Stack.h (arquivo de cabeçalho, contém declarações de modelo):

#ifndef STACK_H
#define STACK_H

template <typename T>
class Stack {
    
    
public:
    Stack();
    void push(const T& value);
    T pop();

private:
    T elements[10];
    int top;
};

#include "Stack.cpp"

#endif

Stack.cpp (arquivo de origem, contém a implementação do modelo):

#ifndef STACK_CPP
#define STACK_CPP

template <typename T>
Stack<T>::Stack() : top(-1) {
    
    }

template <typename T>
void Stack<T>::push(const T& value) {
    
    
    elements[++top] = value;
}

template <typename T>
T Stack<T>::pop() {
    
    
    return elements[top--];
}

#endif

Neste exemplo, tentamos incluir o arquivo de origem no arquivo de cabeçalho Stack.cpp. Isso pode causar os seguintes problemas:

Erro de redefinição : quando vários arquivos de origem incluem o mesmo arquivo de cabeçalho, cada arquivo de origem incluirá Stack.cppa implementação do modelo em , causando um erro de redefinição no momento do link.
A solução é,Coloque a declaração e a implementação do modelo em um arquivo de cabeçalho ou use a instanciação explícita de modelos para evitar erros de redefinição.
A instanciação explícita é uma maneira de dizer ao compilador para instanciar um modelo em um tipo específico e você pode evitar problemas usando a seguinte sintaxe em seus arquivos de origem:

template class Stack<int>;
template class Stack<double>;
// 等等

Isso garante que o template seja instanciado apenas uma vez em um determinado tipo, evitando erros de redefinição.

Embora a instanciação explícita resolva o problema da definição separada de modelos, ela apresenta algumas desvantagens potenciais:

  1. Difícil de manter : se vários tipos diferentes forem usados ​​para instanciação no código, cada tipo precisará ser explicitamente instanciado uma vez no arquivo de origem. isso podeLeva à redundância de código e aumenta a dificuldade de manutenção, especialmente em grandes projetos onde os modelos são usados ​​extensivamente.
  2. Legibilidade reduzida :A sintaxe para instanciação explícita é relativamente complicada e pode reduzir a legibilidade do código. Os programadores precisam entender essa sintaxe especial e fazer instanciações explícitas apropriadas nos arquivos de origem.
  3. Afeta o tempo de compilação : a instanciação explícita faz com que o compilador gere um código de instanciação concreto para o modelo no tempo de compilação, o que aumenta o tempo de compilação. Especialmente quando os modelos são muito usados,O tempo de compilação pode aumentar significativamente
  4. Limitações :A instanciação explícita aplica-se apenas aos modelos que são conhecidos por serem instanciados em um tipo específico. Para alguns modelos genéricos que podem ser usados ​​em tipos diferentes, pode ser impraticável instanciá-los explicitamente para todos os tipos possíveis.

Resumindo, embora a instanciação explícita seja uma forma de resolver o problema da definição de separação de templates, ela pode introduzir algumas inconveniências e problemas potenciais. Portanto, alguns projetos preferem colocar a declaração e implementação do modelo no arquivo de cabeçalho para evitar esses problemas.

Resumo do modelo

vantagem:

  1. Versatilidade e reutilização :Os modelos permitem escrever código genérico que funciona com muitos tipos de dados e estruturas de dados. Essa generalidade promove a reutilização de código e reduz a necessidade de escrever código repetitivo.
  2. Segurança de tipo : os modelos podem ser verificados em tempo de compilação para garantir que o tipo de dados correto seja usado quando o modelo for instanciado. esseAjuda a evitar erros de digitação em tempo de execução
  3. Vantagens de desempenho :O código gerado por modelo é gerado no tempo de compilação a partir de tipos reais, portanto, não há sobrecarga de chamada de função, pode melhorar o desempenho até certo ponto.
  4. Algoritmos genéricos : ambos os algoritmos e contêineres na biblioteca padrão C++ usam modelos, permitindo que os desenvolvedoresÉ conveniente usar classificação comum, pesquisa, travessia e outros algoritmos
  5. Abstração e encapsulamento :Os modelos podem implementar tipos de dados abstratos, encapsulando estruturas de dados e operações, fornecendo um nível mais alto de abstração.
  6. Verificação de erros em tempo de compilação :Erros de modelo geralmente são detectados em tempo de compilação, permitindo que os desenvolvedores detectem e corrijam problemas antecipadamente.

deficiência:

  1. Difícil de entender as mensagens de erro em tempo de compilação : As mensagens de erro do compilador para erros de modelo podem ser muito complexas e difíceis de entender para iniciantes. Isso podeMaior dificuldade de depuração
  2. O tempo de compilação aumenta :O uso de modelos pode aumentar o tempo de compilação, especialmente em grandes projetos. A instanciação de modelos gera várias versões do código em tempo de compilação, possivelmente fazendo com que o compilador gaste mais tempo.
  3. Inchaço de código :A instanciação de modelos pode resultar na geração de várias cópias de código semelhante, aumentando potencialmente o tamanho do executável.
  4. Legibilidade reduzida :Algum código de modelo complexo pode ser difícil de ler e entender, especialmente onde truques de metaprogramação estão envolvidos.
  5. Dificuldade de manutenção : quando as implementações de modelo são separadas em arquivos diferentes,manutenção pode se tornar difícil, especialmente quando se trata de coisas como instanciação explícita.

Considerando tudo, os modelos são uma ferramenta poderosa que pode oferecer grandes vantagens em muitas situações. No entanto, ao usar modelos, os desenvolvedores precisam pesar suas vantagens e desvantagens e fazer uma escolha apropriada com base na situação específica.

Acho que você gosta

Origin blog.csdn.net/kingxzq/article/details/132260644
Recomendado
Clasificación