我有一个创建对象矢量的类。在这个类的解构函数中,我试图释放分配给对象的内存。我试图通过循环遍历向量来做到这一点。所以,如果向量被称为map我正在做:
Building::~Building() {
int i;
for (i=0; i<maps.size(); i++) {
delete[] &maps[i];
}
}
当我运行此程序时,程序会在释放内存时发生段错误。我认为我所做的实际上是删除存储对象的数组而不是对象本身。它是否正确?如果没有任何关于我做错的想法?
答案 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<MrObject> a_vector(5);
现在你有一个带有5个初始化MrObjects的向量。让我们看看当我们清除那个傻逼时会发生什么:
a_vector.clear();
你会注意到所有5个析构函数都被击中了。
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 []
。