双免费或腐败…但为什么?
#include <queue> using namespace std; class Test{ int *myArray; public: Test(){ myArray = new int[10]; } ~Test(){ delete[] myArray; } }; int main(){ queue<Test> q Test t; q.push(t); }
运行这个之后,我得到一个运行时错误“双免费或腐败”。 如果我摆脱了析构函数的内容( delete
)它工作正常。 怎么了?
我们来谈谈在C ++中复制对象。
Test t;
,调用默认的构造函数,它分配一个新的整数数组。 这很好,你的预期行为。
使用q.push(t)
将t
推入队列时会出现问题。 如果您熟悉Java,C#或几乎任何其他面向对象的语言,您可能希望将您创build的对象添加到队列中,但C ++不能这样工作。
当我们看看std::queue::push
方法时 ,我们看到添加到队列中的元素被“初始化为x的副本”。 它实际上是一个全新的对象,它使用复制构造函数复制原始Test
对象的每个成员以创build一个新的Test
。
您的C ++编译器默认为您生成一个拷贝构造函数! 这很方便,但会导致指针成员的问题。 在你的例子中,记住int *myArray
只是一个内存地址; 当myArray
的值从旧对象复制到新对象时,现在将有两个对象指向内存中的同一个数组。 这不是本质上不好,但析构函数然后将尝试删除相同的数组两次,因此“双免费或腐败”的运行时错误。
我如何解决它?
第一步是实现一个复制构造函数 ,它可以安全地将数据从一个对象复制到另一个对象。 为了简单起见,它可能看起来像这样:
Test(const Test& other){ myArray = new int[10]; memcpy( myArray, other.myArray, 10 ); }
现在当你复制testing对象时,一个新的数组将被分配给新的对象,数组的值也将被复制。
尽pipe如此,我们还没有完全摆脱困境。 编译器为您生成的另一种方法可能会导致类似的问题 – 分配。 不同之处在于,在赋值的时候,我们已经有了一个现有的对象,其内存需要进行适当的pipe理。 这是一个基本的赋值运算符实现:
Test& operator= (const Test& other){ if (this != &other) { memcpy( myArray, other.myArray, 10 ); } return *this; }
这里的重要部分是我们将数据从另一个数组复制到这个对象的数组中,保持每个对象的内存不同。 我们也有一个自我分配的检查; 否则,我们会从自己复制到自己,这可能会抛出一个错误(不知道它应该做什么)。 如果我们删除并分配更多的内存,自我分配检查将阻止我们删除我们需要复制的内存。
问题是你的类包含一个被pipe理的RAW指针,但是没有实现三条规则(C ++ 11中有五条)。 因此,你正在(因为复制)(预计)双重删除。
如果你正在学习,你应该学习如何实施三(五)的规则 。 但这不是解决这个问题的正确方法。 您应该使用标准容器对象,而不是尝试pipe理自己的内部容器。 确切的容器将取决于你想要做什么,但std :: vector是一个很好的默认值(如果不是最优的,你可以改变后缀)。
#include <queue> #include <vector> class Test{ std::vector<int> myArray; public: Test(): myArray(10){ } }; int main(){ queue<Test> q Test t; q.push(t); }
你应该使用标准容器的原因是separation of concerns
的separation of concerns
。 你的课应该关心业务逻辑或资源pipe理(不是两者)。 假设Test
是你正在使用的一些类来维护你的程序的状态,那么它就是业务逻辑,它不应该做资源pipe理。 另一方面,如果Test
应该pipe理一个数组,那么您可能需要了解更多关于标准库中可用的内容。
因为第一个析构函数是用于对象q的 ,所以在这种情况下,由new分配的内存将是空闲的。下一次当对象t被调用析构函数时,内存已经是空闲的了在析构函数中删除[] myArray; 将执行它会抛出双倍免费或腐败 。 原因是这两个对象共享相同的内存,所以定义\ copy,赋值和相等的运算符,如上面的答案中所提到的。
你需要定义一个拷贝构造函数,赋值,操作符。
class Test { Test(const Test &that); //Copy constructor Test& operator= (const Test &rhs); //assignment operator }
您在队列中推送的副本指向与原始相同的内存。 当第一个被破坏时,它删除内存。 第二个破坏并尝试删除相同的内存。
你也可以尝试在删除之前检查null
if(myArray) { delete[] myArray; myArray = NULL; }
或者你可以像这样安全地定义所有的删除操作:
#ifndef SAFE_DELETE #define SAFE_DELETE(p) { if(p) { delete (p); (p) = NULL; } } #endif #ifndef SAFE_DELETE_ARRAY #define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p) = NULL; } } #endif
然后使用
SAFE_DELETE_ARRAY(myArray);
嗯,不应该调用删除析构函数,而不是删除[]?