Templates de Classes
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 65 de 104.
Assim como os modelos de função permitem escrever funções independentes de tipo, os modelos de classe permitem criar classes independentes de tipo. É assim que contêineres como std::vector funcionam: você define a estrutura uma vez, e o compilador gera versões específicas para cada tipo que você usa.
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};
int main() {
Box<int> intBox(42);
Box<std::string> strBox("Hello");
std::cout << intBox.getValue() << std::endl; // 42
std::cout << strBox.getValue() << std::endl; // Olá
}Geralmente, você especifica o tipo entre colchetes angulares ao criar objetos a partir de modelos de classe. Desde o C++17, o compilador também pode deduzi-lo pelos argumentos do construtor (Box b(42); cria um Box<int>), mas, quando não há argumentos para deduzi-lo, você precisa escrever o tipo. O compilador então gera uma definição completa de classe para esse tipo específico.
Modelos de classe podem ter vários parâmetros de tipo e até mesmo parâmetros que não são de tipo, como inteiros:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
T& operator[](int index) { return data[index]; }
int size() const { return Size; }
};
FixedArray<double, 5> arr; // Array de 5 valores do tipo double
arr[0] = 3.14;Ao definir funções membro fora da classe, você precisa repetir a declaração do modelo:
template <typename T>
class Container {
T* data;
public:
Container();
~Container();
};
template <typename T>
Container<T>::Container() : data(nullptr) {}
template <typename T>
Container<T>::~Container() { delete data; }Modelos de classe formam a base da programação genérica em C++, permitindo que você escreva estruturas de dados reutilizáveis que funcionam com qualquer tipo.
Desafio
FácilVamos criar um sistema genérico de armazenamento usando modelos de classe para criar um contêiner flexível que pode armazenar qualquer tipo de dado. Você organizará sua classe modelo em um arquivo de cabeçalho e demonstrará sua versatilidade com diferentes tipos no programa principal.
Você criará dois arquivos:
Storage.h: Defina um modelo de classe chamadoStorageque funcione como um contêiner simples para um único valor, com algumas operações úteis:Seu modelo
Storagedeve ter:- Um membro privado para armazenar o valor
- Um construtor que recebe um valor inicial
- Um método
getValue()que retorna o valor armazenado - Um método
setValue()que atualiza o valor armazenado - Um método
isEmpty()que retornatruese o valor for igual ao valor padrão construído do tipo T, efalsecaso contrário
Crie também um modelo de classe chamado
Pair, com dois parâmetros de tipo, que armazena dois valores relacionados:- Membros privados para o primeiro e o segundo valores (que podem ser de tipos diferentes)
- Um construtor que inicializa ambos os valores
- Métodos
getFirst()egetSecond()para recuperar cada valor - Um método
display()que imprime:(<first>, <second>)
main.cpp: Leia quatro entradas (cada uma em uma linha separada):- Um valor inteiro
- Um novo valor inteiro
- Um valor de string
- Um valor double
Demonstre seus modelos da seguinte forma:
- Crie um
Storage<int>com o primeiro inteiro e imprima:Int storage: <value> - Atualize-o com o segundo inteiro usando
setValue()e, em seguida, imprima:Updated: <value> - Crie um
Storage<std::string>com a entrada de string e imprima:String storage: <value> - Verifique se o armazenamento da string está vazio e imprima:
Is empty: <true/false>(imprimatrueoufalse) - Crie um
Pair<std::string, double>com os valores de string e double e, em seguida, chamedisplay() - Crie um
Pair<int, int>com as duas entradas inteiras e, em seguida, chamedisplay()
Por exemplo, com as entradas 42, 100, Hello e 3.14:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)Observe como o mesmo modelo Storage funciona perfeitamente com inteiros e strings, e como Pair pode combinar tipos diferentes. O compilador gera definições de classe separadas para cada combinação de tipos que você usa. Lembre-se de usar a sintaxe template <typename T> para modelos com um único tipo e template <typename T, typename U> para modelos com vários parâmetros de tipo.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// Leia as entradas
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: Crie Storage<int> com o primeiro inteiro e imprima: Int storage: <value>
// TODO: Atualize com o segundo inteiro usando setValue(), imprima: Updated: <value>
// TODO: Crie Storage<std::string> com a entrada de string e imprima: String storage: <value>
// TODO: Verifique se o armazenamento da string está vazio, imprima: Is empty: true/false
// TODO: Crie Pair<std::string, double> com a string e o double, chame display()
// TODO: Crie Pair<int, int> com os dois inteiros, chame display()
return 0;
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online