Plantillas de clases
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 65 de 104.
Al igual que las plantillas de funciones te permiten escribir funciones independientes del tipo, las plantillas de clase te permiten crear clases independientes del tipo. Así es como funcionan los contenedores como std::vector: defines la estructura una vez y el compilador genera versiones específicas para cada tipo que utilices.
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; // Hello
}A diferencia de las plantillas de funciones, debe especificar explícitamente el tipo al crear objetos a partir de plantillas de clase utilizando corchetes angulares. El compilador genera entonces una definición de clase completa para ese tipo específico.
Las plantillas de clase pueden tener múltiples parámetros de tipo e incluso parámetros que no son de tipo, como enteros:
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 doubles
arr[0] = 3.14;Al definir funciones miembro fuera de la clase, debe repetir la declaración de la plantilla:
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; }Las plantillas de clase constituyen la base de la programación genérica en C++, lo que permite escribir estructuras de datos reutilizables que funcionan con cualquier tipo.
Desafío
FácilVamos a construir un sistema de almacenamiento genérico utilizando plantillas de clase para crear un contenedor flexible que pueda albergar cualquier tipo de dato. Organizarás tu clase de plantilla en un archivo de cabecera y demostrarás su versatilidad con diferentes tipos en tu programa principal.
Crearás dos archivos:
Storage.h: Define una plantilla de clase llamadaStorageque actúe como un contenedor simple para un solo valor con algunas operaciones útiles:Tu plantilla
Storagedebe tener:- Un miembro privado para guardar el valor almacenado
- Un constructor que reciba un valor inicial
- Un método
getValue()que devuelva el valor almacenado - Un método
setValue()que actualice el valor almacenado - Un método
isEmpty()que devuelvatruesi el valor es igual al valor construido por defecto del tipo T, yfalseen caso contrario
También crea una plantilla de clase llamada
Paircon dos parámetros de tipo que almacene dos valores relacionados:- Miembros privados para el primer y segundo valor (de tipos potencialmente diferentes)
- Un constructor que inicialice ambos valores
- Métodos
getFirst()ygetSecond()para recuperar cada valor - Un método
display()que imprima:(<first>, <second>)
main.cpp: Lee cuatro entradas (cada una en una línea separada):- Un valor entero
- Un nuevo valor entero
- Un valor de cadena (string)
- Un valor de tipo double
Demuestra tus plantillas mediante:
- La creación de un
Storage<int>con el primer entero, imprimiendo:Int storage: <value> - Su actualización con el segundo entero usando
setValue(), luego imprimiendo:Updated: <value> - La creación de un
Storage<std::string>con la cadena de entrada, imprimiendo:String storage: <value> - La comprobación de si el almacenamiento de la cadena está vacío e imprimiendo:
Is empty: <true/false>(imprimetrueofalse) - La creación de un
Pair<std::string, double>con los valores de cadena y double, luego llamando adisplay() - La creación de un
Pair<int, int>con ambas entradas de enteros, luego llamando adisplay()
Por ejemplo, con las entradas 42, 100, Hello, y 3.14:
Int storage: 42
Updated: 100
String storage: Hello
Is empty: false
(Hello, 3.14)
(42, 100)Observa cómo la misma plantilla Storage funciona perfectamente con enteros y cadenas, y cómo Pair puede combinar diferentes tipos. El compilador genera definiciones de clase separadas para cada combinación de tipos que utilices. Recuerda usar la sintaxis template <typename T> para plantillas de un solo tipo y template <typename T, typename U> para plantillas con múltiples parámetros de tipo.
Hoja de referencia
Las plantillas de clase le permiten crear clases independientes del tipo. El compilador genera versiones específicas para cada tipo que utilice.
Plantilla de Clase Básica
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T getValue() const { return value; }
void setValue(T v) { value = v; }
};Al crear objetos a partir de plantillas de clase, debe especificar explícitamente el tipo utilizando corchetes angulares:
Box<int> intBox(42);
Box<std::string> strBox("Hello");Múltiples Parámetros de Tipo
Las plantillas de clase pueden tener múltiples parámetros de tipo:
template <typename T, typename U>
class Pair {
T first;
U second;
public:
Pair(T f, U s) : first(f), second(s) {}
};Parámetros que no son de Tipo
Las plantillas también pueden aceptar parámetros que no son de tipo, como enteros:
template <typename T, int Size>
class FixedArray {
T data[Size];
public:
int size() const { return Size; }
};
FixedArray<double, 5> arr; // Array de 5 doublesDefinición de Funciones Miembro Fuera de la Clase
Al definir funciones miembro fuera de la clase, repita la declaración de la plantilla:
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; }Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Storage.h"
using namespace std;
int main() {
// Leer entradas
int intValue1;
int intValue2;
string strValue;
double doubleValue;
cin >> intValue1;
cin >> intValue2;
cin >> strValue;
cin >> doubleValue;
// TODO: Crear Storage<int> con el primer entero e imprimir: Int storage: <value>
// TODO: Actualizar con el segundo entero usando setValue(), imprimir: Updated: <value>
// TODO: Crear Storage<std::string> con la entrada de cadena, imprimir: String storage: <value>
// TODO: Comprobar si el almacenamiento de cadena está vacío, imprimir: Is empty: true/false
// TODO: Crear Pair<std::string, double> con cadena y doble, llamar a display()
// TODO: Crear Pair<int, int> con ambos enteros, llamar a display()
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico