Bienvenidos sean a este post, hoy veremos otros de los punteros inteligentes.
En el post anterior vimos como implementar un puntero inteligente basico como es unique_ptr. Lo usamos principalmente para reemplazar a RAII, pero tuvimos que anular el uso de la funcion ver_nombre. Veamos si lo podemos implementar, para ello tomaremos el codigo del post anterior y lo modificaremos de la siguiente manera:
#include <iostream>
#include <string>
#include <memory>
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;
};
void ver_nombre(std::unique_ptr<Producto> v) {
std::cout << v->get_nombre() << std::endl;
}
int main()
{
std::unique_ptr<Producto> video{new Producto};
video->set_nombre("NVIDIA GeForce RTX 4080");
video->set_precio(1100.0);
video->set_disponible(true);
ver_nombre(video);
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;
}
Resumiendolo, la primera clase es el RAII para manejar correctamente los punteros, al ser de tipo generico puede manejar cualquier tipo de dato. Su particularidad mas importante es que elimina al puntero al momento de destruir el objeto, esta misma mecanica la reemplazamos con unique_ptr. La siguiente clase es para crear los objetos de los productos que manipularemos y luego tenemos la funcion para mostrar especificamente la propiedad nombre almacenada en el objeto. Pero ahora le diremos que el tipo sera unique_ptr para poder recibirlo. En el main, primero definimos el objeto y sera manejado por unique_ptr y el tipo es Producto. Lo siguiente es almacenar la informacion en este y llamar a la funcion anterior. Despues tenemos un condicional donde verificamos si esta disponible, muestra el nombre y su valor. En caso contrario, muestra el nombre e indica que no esta disponible. Compilemos y veamos como es su salida:
$ g++ memoria.cpp -o memoria
memoria.cpp: In function ‘int main()’:
memoria.cpp:44:19: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Producto; _Dp = std::default_delete<Producto>]’
44 | ver_nombre(video);
| ~~~~~~~~~~^~~~~~~
In file included from /usr/include/c++/12/memory:76,
from memoria.cpp:3:
/usr/include/c++/12/bits/unique_ptr.h:514:7: note: declared here
514 | unique_ptr(const unique_ptr&) = delete;
| ^~~~~~~~~~
memoria.cpp:34:43: note: initializing argument 1 of ‘void ver_nombre(std::unique_ptr<Producto>)’
34 | void ver_nombre(std::unique_ptr<Producto> v) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~^
$
No se compilo porque unique_ptr tiene una propiedad que nos impide hacerlo, este no puede ser copiado. Por ende, no lo podemos pasar como argumento. Aqui entran en accion los otros punteros, para ello tomaremos el codigo anterior y modificaremos a la funcion ver_nombre y main de la siguiente manera:
void ver_nombre(std::shared_ptr<Producto> v) {
std::cout << v->get_nombre() << std::endl;
}
int main()
{
std::shared_ptr<Producto> video{new Producto};
video->set_nombre("NVIDIA GeForce RTX 4080");
video->set_precio(1100.0);
video->set_disponible(true);
ver_nombre(video);
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;
}
En realidad solo hicimos un cambio, reemplazamos a unique_ptr por shared_ptr. El resto sigue de la misma manera, si lo compilan veran que lo hace correctamente y si lo ejecutan tendran la siguiente salida:
$ ./memoria
NVIDIA GeForce RTX 4080
NVIDIA GeForce RTX 4080 vale 1100 USDD
$
Observen como funciono el llamado a la funcion y el resto del programa. Esto es asi porque shared_ptr tiene una propiedad muy interesante. Al igual que unique_ptr este tambien envuelve al puntero para manejarlo pero lleva una cuenta de las referencias a este y solo las eliminara cuando este sea igual a 0. Permitiendo que podamos tener varias copias de este sin problemas. Vamos a modificar la funcion ver_nombre de la siguiente manera:
void ver_nombre(std::shared_ptr<Producto> v) {
std::cout << v.use_count() << " ojos sobre ";
std::cout << v->get_nombre() << std::endl;
}
En este caso, agregamos una nueva linea donde aplicamos a use_count para mostrar cuantas referencias existen del objeto informado. Si lo compilan nuevamente y ejecutan tendremos la siguiente salida:
$ ./memoria
2 ojos sobre NVIDIA GeForce RTX 4080
NVIDIA GeForce RTX 4080 vale 1100 USDD
$
Los dos objetos que nos muestra son el de main y el que pasamos a la funcion. Y como mencionamos, este eliminara al objeto cuando esta cuenta llegue a 0 o salga del rango o scope. Si bien esta es una herramienta poderosa, tiene sus inconvenientes si no se usa de manera correcta. Tomemos el main del codigo anterior y hagamos el siguiente cambio:
int main()
{
std::shared_ptr<Producto> video{new Producto};
Producto* temp = video.get();
if (true) {
std::shared_ptr<Producto> video2{temp};
video2->set_nombre("AMD Radeon™ PRO W7500");
}
video->set_nombre("NVIDIA GeForce RTX 4080");
video->set_precio(1100.0);
video->set_disponible(true);
ver_nombre(video);
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;
}
Agregamos un nuevo puntero donde le pasamos el objeto anterior y lo liberamos mediante get pero esta no deja de ser propietario del mismo. Tenemos un condicional donde si es verdadero creamos otro objeto con shared_ptr y le asignaremos al puntero anterior para que pueda acceder a las propiedades y metodos de la clase. Lo siguiente es establecer el nombre mediante set_nombre. El resto sigue siendo lo mismo que teniamos antes. Si lo compilan y ejecutan van a obtener un error de violacion de segmento.
Esto es asi porque ambos punteros, video y video2, apuntan al mismo objeto pero ambos no son conciente de esto. Esto implica que cuando modificamos a Producto mediante video2, este afecta a video. Por lo tanto, cuando salgamos del condicional este saldra del scope y se eliminara al objeto que aun es de la propiedad de video. Esto sucedio cuando le pasamos el puntero temp a video2 y video no pudo hacer un seguimiento de esto. La propiedad puede ser solo compartida mediante el constructor de copia u operador de asignacion de shared_ptr. Esto hace que se evite borrar al objeto si esta en uso por otra instancia de shared_ptr. Los punteros compartidos tienen como particularidad compartir propiedad mediante bloques de control, donde cada puntero compartido posee dos punteros. Siendo uno el objeto que maneja y el otro al bloque de control. Pero que es el bloque de control? Este representa un espacio asignado dinamicamente que contiene el recuento de uso del recurso.
En resumen, hoy hemos visto shared_ptr, que es, para que sirve, como se utiliza, y un ejemplo practico para poder verlo en accion. Espero les haya sido 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
