Bienvenidos sean a este post, hoy veremos una particularidad del lenguaje.
En el post anterior mencionamos que se usaba a RAII para ayudar a la hora de asignar y liberar espacios en memoria. Para entender el concepto vamos a usar el codigo que vimos en el post anterior y lo modificaremos de la siguiente manera:
#include <iostream>
#include <algorithm>
template <typename T>
class ManejarArray {
public:
ManejarArray(T* arr): array{arr} {}
~ManejarArray() { delete[] array; }
T& operator[](int ix) { return array[ix]; }
T* raw() { return array; }
private:
T* array;
};
void mostrar_ord()
{
ManejarArray<short> arr{new short[5]};
for (int i=0; i < 5; i++)
{
std::cout << "Valor " << i << ": ";
std::cin >> arr[i];
}
std::sort(arr.raw(), arr.raw() + 5);
for(int i = 0; i < 5; i++)
std::cout << arr[i] << std::endl;
}
int main()
{
mostrar_ord();
return 0;
}
Para este ejemplo, agregamos una clase llamada ManejarArray. Esta tendra un template para manejar cuaalquier tipo de dato en el array que recibira. En la parte privada declaramos un puntero que sera del tipo recibido del template y recibira al array que manejaremos. En la parte publica tenemos un constructor que recibira al array y lo usaremos para asignar a la propiedad privada que comentamos anteriormente. Lo siguiente es el destructor que se encargara de eliminar el array que recibimos de memoria. Por lo tanto, cuando se elimine el objeto se limpiara el array automaticamente, tema que analizamos en el post anterior, y evitando que podamos dejarlo huerfano en memoria. A continuacion, haremos una sobrecarga del operador de array para que podamos manejar las posiciones del array recibido. Y por ultimo, tenemos una funcion que devuelve el valor de la propiedadd privada. En este caso sera la posicion 0 del mismo.
La siguiente modificacion es en la funcion mostrar_ord. En lugar de crear un array de tipo short, creamos un objeto de la clase anterior y le pasamos el tipo short. Luego tenemos un bucle que usaremos para ingresar cinco valores y almacenarlas en el objeto anterior. Lo siguiente es aplicar a sort para ordenar de menor a mayor los valores recibidos en el array. Observen que para pasar el valor inicial y final utilizamos al metodo raw. La opcion final es para indicarle que de la posicion inicial nos desplacemos cinco posiciones. Y finalmente tenemos un bucle para mostrar los valores ordenados. En el main, llamamos a esta funcion. Compilemos y veamos como trabaja:
$ ./memoria
Valor 0: 5
Valor 1: 1
Valor 2: 4
Valor 3: 2
Valor 4: 3
1
2
3
4
5
$
Como podemos ver funciono correctamente, y sabemos que no quedara ningun dato huerfano en memoria. Pero obviamente esto fue a modo ilustrativo, antes que usar una clase como esta es preferible usar a vector, contenedor de STL. La cua hace exactamente lo mismo para manejar la informacion una vez que eliminamos el objeto de este tipo. Como dijimos, esto fue a manera ilustrativa para entender el concepto de RAII. Esto es asi porque manejaremos el recurso desde el momento que se inicializa. Lo cual nos asegura que los datos se eliminaan al finalizar con el objeto e inclusive si ocurre un error y finaliza abruptamente.
Pero para aplicar este concepto es preferible usar un escenario con punteros simples. Para ello vamos a analizar el siguiente ejemplo:
#include <iostream>
#include <string>
class Producto
{
public:
Producto() {}
~Producto() {}
void set_nombre(std::string n) { nombre = n; }
std::string get_nombre() { return nombre; }
void set_precio(float p) { precio = p; }
float get_precio() { return precio; }
void set_disponible(bool d) { disponible = d; }
bool get_disponible() { return disponible; }
private:
std::string nombre;
float precio;
bool disponible;
};
int main()
{
Producto* video{new Producto};
video->set_nombre("NVIDIA GeForce RTX 4080");
video->set_precio(1100.0);
video->set_disponible(true);
if (video->get_disponible()) {
std::cout << video->get_nombre() << " vale ";
std::cout << video->get_precio() << " USDD\n";
} else {
std::cout << video->get_nombre() << " no esta disponible\n";
}
delete video;
return 0;
}
Primero definimos una clase llamada Producto en la cual tendremos tres propiedades para almacenar en la parte privada: nombre, precio y disponibilidad. En la parte publica tenemos los metodos para modificar y recuperar los valores de las propiedades anteriores. En el main, primero creamos un puntero de esta clase y la iniciamos. Para luego establecer mediante los metodos los valores de las propiedades. Despues tenemos un condicional donde verificamos si esta disponible, en caso de existir mosrtamos el nombre y su valor asociado. En caso contrario, mostramos un mensaje indicando que ese producto no esta disponible. Y antes de finalizar boramos este objeto para no dejar nada huerfano en memoria. Compilemos y ejecutemos para ver su salida:
$ ./memoria
NVIDIA GeForce RTX 4080 vale 1100 USDD
$
Como podemos ver funciono correctamente y pueden probar de pasarlo a false para ver si funciona el condicional. Tomemos este codigo y apliquemos RAII. Para ello debemos modificarlo de la siguiente manera:
#include <iostream>
#include <string>
template<typename T>
class ManejarRecursos
{
public:
ManejarRecursos(T* p) : ptr{p} {}
~ManejarRecursos() { delete ptr; }
T& operator*() { return *ptr; }
T* operator->() { return ptr; }
private:
T* ptr;
};
class Producto
{
public:
Producto() {}
~Producto() {}
void set_nombre(std::string n) { nombre = n; }
std::string get_nombre() { return nombre; }
void set_precio(float p) { precio = p; }
float get_precio() { return precio; }
void set_disponible(bool d) { disponible = d; }
bool get_disponible() { return disponible; }
private:
std::string nombre;
float precio;
bool disponible;
};
int main()
{
ManejarRecursos<Producto> video{new Producto};
video->set_nombre("NVIDIA GeForce RTX 4080");
video->set_precio(1100.0);
video->set_disponible(true);
if (video->get_disponible()) {
std::cout << video->get_nombre() << " vale ";
std::cout << video->get_precio() << " USDD\n";
} else {
std::cout << video->get_nombre() << " no esta disponible\n";
}
return 0;
}
Para este caso, solo agregamos una clase manejadora de recursos. Esta tiene un template para manejar cualquier tipo de datos. Tenemos un puntero del tipo recibido del template en la parte privada. En la parte publica tenemos el constructor para inciarla con un puntero recibido. Luego un destructor para eliminar el puntero creado. Tambien dos sobrecargas para el operador de puntero y el de funciones desde puntero. Estos son necesarios para que podamos utilizarlo desde esta clase. En el primer caso devuelve al puntero y en el segundo al contenido de este. La clase producto sigue siendo la misma, y en el main hicimos solo dos cambios. Eliminamos el puntero de la clase y lo reemplazamos por un objeto de la nueva clase. Este sera del tipo Producto y lo creamos con este. El resto sigue siendo lo mismo pero ahora no necesitamos borrar a este objeto porque de esto se encarga el destructor. Si lo compilan y ejecutan deben obtener la misma salida. Beneficio de trabajar de esta manera, en todo momento sin importar el tipo de puntero que tengamos sera eliminado cuando salgamos de su rango o scope.
En resumen, hoy hemos visto RAII, que es, para que sirve, como se aplica, un ejemplo para aplicarlo con un array y luego otro ejemplo para aplicarlo con un puntero simple. Espero les haya resultado de utilidad sigueme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.


Donatión
It’s for site maintenance, thanks!
$1.50
