解除分配存储在向量中的对象?

时间:2010-09-27 22:50:14

标签: c++ memory-management vector

我有一个创建对象矢量的类。在这个类的解构函数中,我试图释放分配给对象的内存。我试图通过循环遍历向量来做到这一点。所以,如果向量被称为map我正在做:

Building::~Building() {
    int i;
    for (i=0; i<maps.size(); i++) {
        delete[] &maps[i];
    }
}

当我运行此程序时,程序会在释放内存时发生段错误。我认为我所做的实际上是删除存储对象的数组而不是对象本身。它是否正确?如果没有任何关于我做错的想法?​​

7 个答案:

答案 0 :(得分:17)

这取决于矢量的定义方式。

如果地图是vector<myClass*>,则删除每个元素的内容类似于:

for ( i = 0; i < maps.size(); i++)
{
    delete maps[i];
}

如果地图是vector<myClass>,我认为您不需要删除单个元素。

答案 1 :(得分:13)

很难从您使用的术语中分辨出来,并且代码确切地呈现了正在发生的事情。所以也许一些例子会帮助你。

数组新建和数组删除

您提出的new []delete []有什么问题?这些人用于分配/解除分配事物的数组。那些东西可能是POD,或者它们可能是完全成熟的物体。对于对象,在解除分配时,它们将在分配和析构函数后调用构造函数。

让我们举一个人为的例子:

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;
};

现在我们可以用MrObject做一些有趣的事情了。

对象数组

首先让我们创建一个漂亮而简单的数组:

MrObject* an_array = new MrObject[5];

这给了我们一个包含5个MrObjects的数组,所有这些都很好地初始化了。如果我们要删除该数组,我们应该执行数组删除,然后调用每个MrObject的析构函数。我们试试吧:

delete [] an_array;

但是,如果我们搞砸了,只是做了正常删除怎么办?那么现在是自己尝试的好时机

delete an_array;

你会看到只有第一个析构函数被调用。那是因为我们没有删除整个数组,只删除了第一个条目。

有时好吧。这真的是未定义的。当你使用数组new时,使用数组形式的删除,同样只使用普通旧的new和删除。

对象的向量

好的,这很有趣。但是现在让我们来看看std :: vector。你会发现这个人会为你管理记忆,当他超出范围时,他所掌握的一切都是如此。我们带他去试驾:

std::vector<MrObject> a_vector(5);

现在你有一个带有5个初始化MrObjects的向量。让我们看看当我们清除那个傻逼时会发生什么:

a_vector.clear();

你会注意到所有5个析构函数都被击中了。

指针到对象的向量

噢,你说,但现在让我们开心。我想要std :: vector的所有优点,但也希望自己管理所有内存!那么也有一条线:

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;
}

看到这有点痛苦。但它可能很有用,您可以在创建MrObject时使用非默认构造函数。您可以将派生的MrObjects放在那里。好吧,你可以看到天空的极限。可是等等!你创造了那个记忆,你最好管理它。你需要循环遍历向量中的每个条目并自己清理:

for (size_t idx = 0; idx < a_vector_of_pointers.size(); idx++) {
   delete a_vector_of_pointers[idx];
}

答案 2 :(得分:6)

在C ++中,您只能通过指针删除数据。你已经使用&amp;运算符,但是如果你的向量不包含指向机器堆上分配的内存的指针(不是堆栈,就像你有正常变量声明时的方法那样)那么你可以尝试删除它,但是你会遇到未定义的行为(希望会导致程序崩溃)。

当您插入向量时,向量会调用类的复制构造函数,而您实际上是在插入对象的副本。如果您的功能的唯一目的如下:

void insertObj(obj & myObject)
{
  myVector.insert(myObject);
}

然后意识到在这个范围内有两个 obj:你通过引用传入的那个,以及向量中的副本。相反,如果我们通过值而不是通过引用传递myObject,那么我们可以说该范围中存在两个对象副本,并且一个副本存在于调用者中。在这3个实例中的每个实例中,它们不是相同的对象。

如果您将指针存储在容器中,则向量将创建指针的副本( NOT 对象的副本),并将复制的指针插入向量中。通过指针将元素插入容器通常不是一个好习惯,除非你知道该对象将至少存在,直到容器完成它为止。例如,

void insert()
{
  Obj myObj;
  myVector.insert(&myObj);
}

可能是一个非常糟糕的主意,因为你在向量中有一个指针指向一个在超出范围时自动销毁的对象!

重点是,如果你对你的对象进行了malloc'd或new'd,那么你需要释放或删除它。如果你在堆栈上创建它,那么什么都不做。载体在被破坏时会处理它。

要更深入地了解基于堆栈的分配与基于堆的分配,请参阅我的答案: How does automatic memory allocation actually work in C++?

答案 3 :(得分:1)

for(std::vector<MyObjectClass>::iterator beg = myVector->begin(), end = myVector->end(); beg != end; beg++)
{
    delete *beg;
}
myVector->clear();

答案 4 :(得分:1)

我决定把我的评论变成一个答案(以及其他很好的答案),所以就这样了。

我会再次注意,这个案例涉及对象的继承。

当您删除由Base指针指向的Derived对象数组时,如下所示:

Base* pArr = new Derived[3];

delete [] pArr;

编译器在“引擎盖下”做的是生成以下代码:

//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(); 
}

现在,当这样做时,我们会得到未定义的行为。处理数组只是简单地处理偏移,因此当发生这种循环时,在循环的每次迭代中,数组的指针根据Base的大小递增 - &gt;这就是事物变得“未定义”的地方。 在“简单”(但不太常见)的情况下,Derived类不添加任何自己的成员,其大小为Base的大小 - &gt;所以事情可能(我想并不总是)运作良好。 但是(!!)当你向Derived类添加至少一个成员时,它的大小会增加,导致每次迭代中的偏移量增量都是错误的。

为了说明这种情况,我创建了以下Base和Derived对象。 请注意,如果派生不包含 m_c 成员,则删除操作会顺利进行(注释并自行查看),YET一旦添加,我得到了一个分段错误(这是未定义的行为)。

#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;
}

答案 5 :(得分:0)

很难从你的问题中说出maps的签名是什么。我猜您要使用delete[],因为您还使用了new[]。那么这是否意味着你的矢量成员本身就是一个集合?假设它是,那么你有这样的东西:

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]);  
  }
}

在这种情况下,你的析构函数几乎是正确的;您只需要&maps[i]更改为maps[i]

Building::~Building() {
    int i;
    for (i=0; i<maps.size(); i++) {
        delete[] maps[i];
    }
}

但在C ++中,我们很少喜欢这样做。首先,除非您实际尝试实现std::vector之类的内容,否则您很少明确地使用new[]delete[]。例如,您可以使用std::vector。在这种情况下,您不需要执行显式内存管理。你的课将如下:

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));  
  }
}

在这种情况下没有用户定义的析构函数,因为std::vector已经很好地管理了自己的内存。

答案 6 :(得分:-2)

如果您正在使用std::vector,那么您可以让vector的析构函数处理它,假设所述{{1}中有“对象”(而不是指向对象的指针) }。

- 或 -

如果您使用标准数组作为“vector”:

vector”变体的目的是解除分配整个数组,因此无需像你一样拥有delete []循环。

如果使用标准C / C ++数组,“for”应该为您完成。 “delete [] maps”不应用于解除分配STL []