¿Cuando se borran los punteros en C++?

Por una vez, intento optimizar algo de memoria en C++ y estoy encontrándome con dudas respecto a cuando he de hacer los deletes...

Duda 1:
Yo creo que una clase que tiene varios punteros. En el destructor me cargo los punteros que uso y tal.
Cuando la creo con el puntero en plan Clase c = new Clase(), después me la cargo con delete p y ahí es cuando se llama al destructor... ¿Y si la creo como estática? Es decir, Clase c(), ¿Al terminar el método se elimina llamado al constructor?

Duda 2:
Cuando tengo un vector, list o alguna función semejante de la STL con punteros, en plan vector<Prueba *> * pruebas;
¿Cuando hago un delete pruebas, se carga todos los punteros que contenga o deja la memoria por ahí y debería ir eliminándola antes de cargarme el vector?
A ver, como norma tendrás que hacer un delete por cada new que uses, si creas un puntero Clase* c = new Clase(), liberas la memoria cuando lo necesites con un delete c y ahí *creo* que no llama al destructor. El destructor se ejecutará automáticamente en caso que la crees como estática y no se vaya a utilizar más.

Por ejemplo, vas a crear una clase para almacenar una imagen, y uno de los miembros de tu clase es un puntero doble de Byte (para guardar los tonos de cada pixel)

Class Imagen{
private:
byte** pixeles;
}


Ahora haces un contructor parametrizado que reserve memoria en funcion del alto y ancho de la imagen:

Imagen::Imagen(int h, int w){
pixeles = new byte*[h];
for(int i=0;i<h;i++){
pixeles[i] = new byte[w];
}
}


Y un constructor vacío por si no quieres reservar nada

Imagen::Imagen(){
pixeles = NULL;
}


El destructor tendrá que encargarse de liberar la memoria que hayas podido reservar en el constructor, así que puedes hacer:

Imagen::~Imagen(){
if(pixeles != NULL){
for(int i=0;int<h;i++){
delete pixeles[i];
}
delete pixeles;
pixeles = NULL;
}
}


De esta forma si has usado el constructor parametrizado para crear una clase de manera estática, cuando la clase no se vaya a utilizar más se ejecutará de forma automática el destructor, que se encargará de liberar la memoria reservada si por el constructor si es necesario.

Por otro lado, si haces un Clase c = new Clase() deberás de encargarte de liberar la memoria tú con un delete c, ya que al reservarla manualmente se presupone que puedes querer usarla en cualquier parte de tu programa.

Respecto de la segunda pregunta, no estoy seguro, así que a ver si alguien lo aclara.
¿Es óptimo pasar el destructor constantemente para liberar memoria? ¿No es mejor pasarlo en función de las necesidades de reutilización de la memoria?
Uf.... veamos:

Los objetos que tu llamas estáticos se destruyen automáticamente cuando salen de ámbito (go out of scope). Es decir:

{
    Tarari c;
    ...
} // aquí se destruye automáticamente c


Si haces new, siempre tienes que hacer un delete. De modo que si tienes un vector<T *> *, para destruirlo correctamente debes:

- Hacer delete de los punteros que hayas almacenado en el vector
- Después, hacer delete del vector

Cuando haces delete de un objeto EVIDENTEMENTE se ejecuta su destructor.

¿Es óptimo pasar el destructor constantemente para liberar memoria? ¿No es mejor pasarlo en función de las necesidades de reutilización de la memoria?


Esto no lo entendí, pero me da la sensación de que la respuesta es "Estás muy confundido".

Y una vez dicho esto.... lo que tienes que hacer es NO USAR PUNTEROS (a no ser que sepas lo que estás haciendo, y evidentemente no lo sabes porque las dudas son básicas). Lo que tienes que usar es el template std::tr1::shared_ptr que está en cualquier compilador medio decente. Esta clase se encarga de llamar a delete cuando ya no se necesita el puntero. Así de fácil de usar:

#include <iostream>
#include <tr1/memory>

struct A
{
   A() { std::cout << "ctor" << std::endl; }
   ~A() { std::cout << "dtor" << std::endl; }
   void test() const { std::cout << "test" << std::endl; }
};

void f1(const std::tr1::shared_ptr<A> a)
{
   a->test();
}

void f2(const A * const a)
{
   a->test();
}

int main(int argc, char *argv[])
{
   int i(0);

   std::cout << i++ << std::endl;
   std::tr1::shared_ptr<A> a(new A);
   std::cout << i++ << std::endl;
   {
      std::cout << i++ << std::endl;
      std::tr1::shared_ptr<A> aa(a);
      std::cout << i++ << std::endl;
      f1(aa);
      std::cout << i++ << std::endl;
   }
   std::cout << i++ << std::endl;
   f2(a.get());
   std::cout << i++ << std::endl;

   return 0;
}


Como se puede ver en el ejemplo solo se ejecuta un constructor y un destructor (dado que solo creamos un objeto). Además es 'transparente' en cuanto a que podemos seguir usándolo como un puntero (*, ->, ...)

Saludos.

-------

Es más, como muestra vale un botón. El caso de crear un vector con varios punteros y liberarlos:

std::vector<A *> * v(new std::vector<A *>);
v->push_back(new A);
v->push_back(new A);
v->push_back(new A);
v->push_back(new A);
v->push_back(new A);
// ... usar v y los 'A' que hemos creado
for (std::vector<A *>::iterator i(v->begin()), i_end(v->end()) ; i != i_end ; ++i)
    delete *i;
delete v;


Es decir, comparado con lo siguiente, un puñetero engorro:

std::vector<std::tr1::shared_ptr<A> > v(new std::vector<std::tr1::shared_ptr<A> >);
v->push_back(new A);
v->push_back(new A);
v->push_back(new A);
v->push_back(new A);
// ... usar normalmente v y los 'A' creados
// Ops... se destruye todo automáticamente


Saludos.
Ferdy escribió:
¿Es óptimo pasar el destructor constantemente para liberar memoria? ¿No es mejor pasarlo en función de las necesidades de reutilización de la memoria?


Esto no lo entendí, pero me da la sensación de que la respuesta es "Estás muy confundido".


[enfado1] Llevo años sin programar en C++, así que estoy muy desconectado del lenguaje. Por lo que entiendo cuando usas la orden delete sobre un objeto, este se libera de memoria. La pregunta es si es mas óptimo liberar memoria cada vez que se usa una "orden delete", o por el contrario es mas óptimo que cuando se usa una "orden delete" se apunte el objeto como destruible y cuando se necesite memoria se liberen todos los objetos de la lista, como hace por ejemplo el recolector de basura de Java, que pasa cuando le parece y no cuando lo llamas.
Eso no ayudaría en nada por varias razones: La única diferencia entre marcar un objeto como destruible y destruirlo es símplemente ejecutar o no el destructor. Esa simple diferencia limitaría mucho la utilidad de los destructores.

Si le das un par de vueltas, verás que no solo no es buena idea si no que es bastante mala.

Por otro lado, el recolector de basura de Java es bastante más complicado que todo esto.

- ferdy
Korso10 escribió:A ver, como norma tendrás que hacer un delete por cada new que uses, si creas un puntero Clase* c = new Clase(), liberas la memoria cuando lo necesites con un delete c y ahí *creo* que no llama al destructor. El destructor se ejecutará automáticamente en caso que la crees como estática y no se vaya a utilizar más.


No. Por norma general, si el compilador puede determinar la clase que estás destruiendo, si que llama al destructor. En este caso se puede determinar pq el punter es de clase Clase.

PrivateJerson escribió:[enfado1] Llevo años sin programar en C++, así que estoy muy desconectado del lenguaje. Por lo que entiendo cuando usas la orden delete sobre un objeto, este se libera de memoria. La pregunta es si es mas óptimo liberar memoria cada vez que se usa una "orden delete", o por el contrario es mas óptimo que cuando se usa una "orden delete" se apunte el objeto como destruible y cuando se necesite memoria se liberen todos los objetos de la lista, como hace por ejemplo el recolector de basura de Java, que pasa cuando le parece y no cuando lo llamas.


Cuando un objeto no tenga razón de exister destruyelo -> menos objetos, menos follones. Una vez hayas terminado la aplicación, si descubres que tienes problemas de memoria sobrecarga el operador new y delete para gestionar tu mismo la memoria. Por regla general no es necesario, el gestor de C ya es bastante bueno en situaciones normales.

Dicho de otro modo los objetos corresponden a la parte lógica de la aplicación, con los operadores new y delete puedes acceder a la parte física.
Eteream escribió:
Korso10 escribió:A ver, como norma tendrás que hacer un delete por cada new que uses, si creas un puntero Clase* c = new Clase(), liberas la memoria cuando lo necesites con un delete c y ahí *creo* que no llama al destructor. El destructor se ejecutará automáticamente en caso que la crees como estática y no se vaya a utilizar más.


No. Por norma general, si el compilador puede determinar la clase que estás destruiendo, si que llama al destructor. En este caso se puede determinar pq el punter es de clase Clase.


Si, después lo pensé bien y es lo más lógico. Lo que me hacía dudar es si la clase no tuviese definido el destructor, ¿que pasa al hacer delete? ¿Se llama a uno genérico? ¿No se destruye?
No. Por norma general, si el compilador puede determinar la clase que estás destruiendo, si que llama al destructor. En este caso se puede determinar pq el punter es de clase Clase.


¿Cuándo no puede determinar qué clase estás destruyendo?

Si, después lo pensé bien y es lo más lógico. Lo que me hacía dudar es si la clase no tuviese definido el destructor, ¿que pasa al hacer delete? ¿Se llama a uno genérico? ¿No se destruye?


Si no existe un destructor no se hace nada adicional. Se llevan a cabo las tareas normales como destruir bases y miembros.

- ferdy
Ferdy escribió:Si no existe un destructor no se hace nada adicional. Se llevan a cabo las tareas normales como destruir bases y miembros.

- ferdy


Entonces, en la clase que puse antes de ejemplo:

Class Imagen{
    private:
        byte** pixeles;
}


Imagen::Imagen(int h, int w){
    pixeles = new byte*[h];
    for(int i=0;i<h;i++){
        pixeles[i] = new byte[w];
    }
}


Si yo declarase un objeto Imagen y esta clase no tuviese destructor, ¿que ocurriría exactamente? Por lo que he entendido, ¿se destruiría el puntero pero la memoria seguiría estando reservada?
Korso10 escribió:
Ferdy escribió:Si no existe un destructor no se hace nada adicional. Se llevan a cabo las tareas normales como destruir bases y miembros.

- ferdy


Entonces, en la clase que puse antes de ejemplo:

Class Imagen{
    private:
        byte** pixeles;
}


Imagen::Imagen(int h, int w){
    pixeles = new byte*[h];
    for(int i=0;i<h;i++){
        pixeles[i] = new byte[w];
    }
}


Si yo declarase un objeto Imagen y esta clase no tuviese destructor, ¿que ocurriría exactamente? Por lo que he entendido, ¿se destruiría el puntero pero la memoria seguiría estando reservada?


correcto, destruirias el puntero, la referencia, pero la memoria se quedaria asignada y tendiras un bonito memory leak...
10 respuestas