pushback - vector vector c++
Desasignando objetos almacenados en un vector? (7)
Depende de cómo se define el vector.
Si maps es un vector<myClass*>
elimina cada elemento con algo similar a:
for ( i = 0; i < maps.size(); i++)
{
delete maps[i];
}
Si maps es un vector<myClass>
, no creo que deba eliminar los elementos individuales.
Tengo una clase que crea un vector de objetos. En el deconstructor para esta clase estoy tratando de desasignar la memoria asignada a los objetos. Estoy tratando de hacer esto simplemente recorriendo el vector. Entonces, si el vector se llama mapas, estoy haciendo:
Building::~Building() {
int i;
for (i=0; i<maps.size(); i++) {
delete[] &maps[i];
}
}
Cuando ejecuto esto, el programa falla al desasignar la memoria. Creo que lo que estoy haciendo es eliminar la matriz que almacena los objetos y no los objetos mismos. ¿Es esto correcto? Si no hay alguna idea de lo que estoy haciendo mal?
Es difícil decir a partir de su pregunta cuál es la firma de los maps
. Supongo que quiere usar delete[]
porque también usó new[]
. Entonces, ¿eso significa que los miembros de tu vector son en sí una colección? suponiendo que así sea, entonces tienes algo como esto:
class Building {
public:
typedef int* maps_t;
private:
std::vector<maps_t> maps;
public:
Building();
~Building();
};
Building::Building(size_t num_maps) {
for(;num_maps; --num_maps)
{
maps.push_back(new Building::maps_t[10]);
}
}
en ese caso, tu destructor está casi en lo correcto; solo necesita cambiar &maps[i]
a maps[i]
.
Building::~Building() {
int i;
for (i=0; i<maps.size(); i++) {
delete[] maps[i];
}
}
Pero en C ++, rara vez nos gusta hacer las cosas de esa manera. Por un lado, a menos que realmente esté tratando de implementar algo como std::vector
, rara vez desea usar new[]
o delete[]
explícitamente. Puede, por ejemplo, usar std::vector
. No es necesario realizar ninguna gestión de memoria explícita en ese caso. Tu clase se verá así:
class Building {
public:
typedef std::vector<int> maps_t;
private:
std::vector<maps_t> maps;
public:
Building();
};
Building::Building(size_t num_maps) {
for(;num_maps; --num_maps)
{
maps.push_back(Building::maps_t(10));
}
}
No hay destructor definido por el usuario en este caso, porque std::vector
ya maneja bastante bien su propia memoria.
Es difícil saber a partir de la terminología que ha utilizado y el código presentado exactamente lo que está sucediendo. Entonces quizás algunos ejemplos te ayuden.
Matriz nueva y eliminación de matriz
¿Qué pasa con new []
y delete []
preguntas? Estos tipos se utilizan para asignar / desasignar matrices de cosas. Esas cosas podrían ser POD o podrían ser objetos completos. Para los objetos llamarán al constructor después de la asignación y el destructor mientras se desasigna.
Tomemos un ejemplo artificial:
class MrObject
{
public:
MrObject() : myName(new char[9]) { memcpy(myName, "MrObject", 9); }
virtual ~MrObject() { std::cout << "Goodbye cruel world!/n"; delete [] myName; }
private:
char* myName;
};
Ahora podemos hacer cosas divertidas con MrObject.
Matrices de objetos
Primero, creemos una matriz agradable y simple:
MrObject* an_array = new MrObject[5];
Esto nos da una matriz de 5 MrObjects, todos muy bien inicializados. Si queremos eliminar esa matriz, debemos realizar una eliminación de matriz, que a su vez llamará al destructor para cada MrObject. Probemos eso:
delete [] an_array;
¿Pero qué pasa si nos equivocamos y simplemente hicimos una eliminación normal? Bueno, ahora es un buen momento para probarlo por ti mismo
delete an_array;
Verás que solo se llama al primer destructor. Eso es porque no eliminamos toda la matriz, solo la primera entrada.
Bueno, a veces. Es realmente indefinido lo que sucede aquí. Lo que se lleva es usar la forma de matriz de eliminar cuando se usa una matriz nueva, ídem para simplemente nueva y nueva.
Vectores de objetos
OK, eso fue divertido. Pero echemos un vistazo al std :: vector ahora. Descubrirá que este tipo administrará la memoria para usted, y cuando se salga del alcance, también lo hará todo lo que tenga. Vamos a llevarlo a dar un paseo de prueba:
std::vector<MrObject> a_vector(5);
Ahora tienes un vector con 5 MrObjects inicializados. Veamos qué pasa cuando eliminamos a ese chupador:
a_vector.clear();
Notarás que los 5 destructores fueron golpeados.
Vectores de punteros a los objetos
Oooooh dices, pero ahora vamos a ser elegante. ¡Quiero toda la bondad del std :: vector, pero también quiero administrar todo el recuerdo yo mismo! Bueno, hay una línea para eso también:
std::vector<MrObject*> a_vector_of_pointers(5);
for (size_t idx = 0; idx < 5; idx++) {
// note: it''s just a regular new here, not an arra
a_vector_of_pointers[idx] = new MrObject;
}
Ver eso fue un poco más doloroso. Pero puede ser útil, puede usar un constructor no predeterminado al crear MrObject. Podría poner MrObjects derivados en su lugar. Bueno, como puedes ver, el cielo es el límite. ¡Pero espera! Usted creó esa memoria, la mejor gestión posible. Deseará recorrer cada entrada en el vector y realizar la limpieza después de usted:
for (size_t idx = 0; idx < a_vector_of_pointers.size(); idx++) {
delete a_vector_of_pointers[idx];
}
for(std::vector<MyObjectClass>::iterator beg = myVector->begin(), end = myVector->end(); beg != end; beg++)
{
delete *beg;
}
myVector->clear();
Si está usando std::vector
, puede dejar que sea manejado por el destructor para vector
, suponiendo que hay "objetos" (y no punteros a objetos) en dicho vector
.
- o -
Si está utilizando una matriz estándar como el " vector
":
El propósito de la variación " delete []
" es desasignar una matriz completa y, por lo tanto, evitar la necesidad de tener un bucle for
como tú.
Si usa matrices C / C ++ estándar, " delete [] maps
" debería hacerlo por usted. " []
" no se debe usar para desasignar vector
STL s.
En C ++, solo puede eliminar datos por puntero. Has logrado esto usando el operador &, pero si tu vector no contiene punteros que apuntan a la memoria asignada en el montón de máquinas (no en la pila, como es el método cuando tienes una declaración de variable normal) entonces puedes TRATAR de bórrelo, pero encontrará un comportamiento indefinido (que con suerte causará un bloqueo del programa).
Cuando insertas en un vector, el vector llama al constructor de copia de la clase y en realidad estás insertando una copia del objeto. Si tiene una función cuyo único propósito es el siguiente:
void insertObj(obj & myObject) { myVector.insert(myObject); }
Entonces date cuenta de que hay dos obj en este ámbito: el que pasaste por referencia, y la copia en el vector. Si, en cambio, hubiéramos pasado en myObject por valor y no por referencia, entonces podríamos decir que existen dos copias del objeto en este ámbito, y que existe en la persona que llama. En cada una de estas 3 instancias, no son el mismo objeto.
Si en su lugar está almacenando punteros en el contenedor, el vector creará una copia del puntero ( NO una copia del objeto) e insertará el puntero copiado en el vector. Por lo general, no es una buena práctica insertar elementos en un contenedor con un puntero a menos que sepa que el objeto vivirá al menos hasta que el contenedor termine con él. Por ejemplo,
void insert() { Obj myObj; myVector.insert(&myObj); }
Es probablemente una muy mala idea, ya que tendrías un puntero en el vector que apunta a un objeto que se destruye automáticamente cuando sale del alcance.
El punto es que, si malloc''d o new''d su objeto, necesita liberarlo o eliminarlo. Si lo creó en la pila, entonces no haga nada. El vector se encargará de eso cuando se destruya.
Para una comprensión más profunda de la asignación basada en la pila frente a la asignación basada en el montón, consulte mi respuesta aquí: ¿Cómo funciona realmente la asignación automática de memoria en C ++?
Decidí convertir mi comentario en una respuesta (junto con las otras excelentes respuestas aquí), así que aquí va.
Me gustaría volver a señalar que este caso se refiere a la herencia del objeto.
Cuando elimina una matriz de objeto Derivado, apuntado por un puntero Base, de la siguiente manera:
Base* pArr = new Derived[3];
delete [] pArr;
Lo que hace el compilador "debajo del capó" es generar el siguiente código:
//destruct the objects in *pArr in the inverse order
//in which they were constructed
for (int i = the number of elements in the array - 1; i >= 0; --i)
{
pArr[i].Base::~Base();
}
Ahora, al hacerlo, obtenemos un comportamiento indefinido. Tratar con matrices es simplemente tratar con compensaciones, de modo que cuando se produce este bucle, en cada iteración del ciclo, el puntero de la matriz se incrementa de acuerdo con el tamaño de Base -> y aquí es donde las cosas se vuelven "indefinidas". En el caso "simple" (aunque menos común) donde la clase Derivada no agrega ningún miembro propio, su tamaño es como el tamaño de Base -> así que las cosas podrían funcionar (supongo que no siempre). Pero (!!) cuando agrega al menos un miembro a la clase Derivada, su tamaño aumenta, lo que hace que el incremento de desplazamiento en cada iteración sea incorrecto.
Para ilustrar este caso, he creado los siguientes objetos Base y Derivados. Tenga en cuenta que en el caso de que Derived no contenga el miembro m_c , la operación de eliminación funciona bien ( coméntelo y compruébelo usted mismo), SIN EMBARGO , una vez que lo agrega, obtengo un error de segmentación (que es el comportamiento indefinido).
#include <iostream>
using namespace std;
class Base
{
public:
Base(int a, int b)
: m_a(a)
, m_b(b)
{
cout << "Base::Base - setting m_a:" << m_a << " m_b:" << m_b << endl;
}
virtual ~Base()
{
cout << "Base::~Base" << endl;
}
protected:
int m_a;
int m_b;
};
class Derived : public Base
{
public:
Derived()
: Base(1, 2) , m_c(3)
{
}
virtual ~Derived()
{
cout << "Derived::Derived" << endl;
}
private:
int m_c;
};
int main(int argc, char** argv)
{
// create an array of Derived object and point them with a Base pointer
Base* pArr = new Derived [3];
// now go ahead and delete the array using the "usual" delete notation for an array
delete [] pArr;
return 0;
}