Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 llamada Storage que actúe como un contenedor simple para un solo valor con algunas operaciones útiles:

    Tu plantilla Storage debe 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 devuelva true si el valor es igual al valor construido por defecto del tipo T, y false en caso contrario

    También crea una plantilla de clase llamada Pair con 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() y getSecond() para recuperar cada valor
    • Un método display() que imprima: (<first>, <second>)
  • main.cpp: Lee cuatro entradas (cada una en una línea separada):
    1. Un valor entero
    2. Un nuevo valor entero
    3. Un valor de cadena (string)
    4. Un valor de tipo double

    Demuestra tus plantillas mediante:

    1. La creación de un Storage<int> con el primer entero, imprimiendo: Int storage: <value>
    2. Su actualización con el segundo entero usando setValue(), luego imprimiendo: Updated: <value>
    3. La creación de un Storage<std::string> con la cadena de entrada, imprimiendo: String storage: <value>
    4. La comprobación de si el almacenamiento de la cadena está vacío e imprimiendo: Is empty: <true/false> (imprime true o false)
    5. La creación de un Pair<std::string, double> con los valores de cadena y double, luego llamando a display()
    6. La creación de un Pair<int, int> con ambas entradas de enteros, luego llamando a display()

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 doubles

Definició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;
}
quiz iconPonte a prueba

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