删除对象时调试断言失败

Debug Assertion failure when deleting object

本文关键字:断言 失败 调试 对象 删除      更新时间:2023-10-16

我最近在用C++编写的一些代码中遇到了一个问题,我无法找到解决方案。乍一看,这个问题似乎很简单,但由于某种原因,程序抛出了一个错误,我无法解释原因。我不会复制粘贴我遇到错误的原始代码(因为它太麻烦了),但这里有一个简化版本,它在相同的上下文中表现出完全相同的行为:

#include<vector>
using namespace std;
class A_class
{
  bool *heap_space;    //can be any type of pointer
public:
  A_class()  { heap_space = new bool[4]; }
  A_class(const A_class&)  { heap_space = new bool[4]; }
  ~A_class()  { delete[] heap_space; }
};
void main()
{
  vector<A_class> ObjArr(5);
  vector<A_class>::iterator iTer = ObjArr.begin() + x; 
  //where x can be any number from 0 to 3
  ObjArr.erase(iTer);
}

我知道代码看起来非常简单,但我似乎无法找出引发异常的原因。每次我尝试运行该代码时,该代码都会在运行时抛出一条"调试断言失败!"消息,其中包含"Expression:BLOCK_TYPE_IS_VALID(pHead->nBlockUse)"。

在容器的擦除方法中,消息不会立即出现,这可能也很有用。只有在矢量超出范围后才会显示。因此,我一直试图通过各种方法修复这个错误,在矢量超出范围之前添加代码(比如在擦除后立即重新插入一个新元素),但没有成功。此外,经过一点实验,我发现只有在擦除了矢量的最后一个元素(ObjArr.end()-1)之外的任何元素后,消息才会出现。希望这些提示能有所帮助。如果有人知道为什么会发生这种情况,请向我解释。我确信我只是犯了一个新手错误,因为这似乎很容易弄清楚,但我做不到。

上面的代码是在Windows 7下使用Visual Studio 2013编译的。

有趣的是,这是因为您没有提供赋值运算符。你为什么需要一个?让我们来考虑一个向量。矢量对象有一个指向5个A_class数组的指针。它们是默认构造的,这是没有问题的,因为你已经定义了它。现在我们删除:

A_class }
A_class }
A_class }-- Erase one of these
A_class }
A_class }

有趣的是,如果我们删除最后一个,我们不会看到问题,只有当我们删除索引0到3中的一个时。为什么?好吧,当我们删除,比如说,索引2,我们得到他的:

A_class
A_class
-- empty space with size = sizeof(A_class)
A_class
A_class

为了协调这个空间,在擦除结束时,std::vector使用赋值运算符来修复数组。所以index[2] = index[3]index[3] = index[4]。现在,因为您没有声明赋值运算符,所以它将使用默认值,包括删除index[4]。这很糟糕,因为index[4]会给index[3]它的指针,然后删除它,结果是:

A_class // heap_space okay
A_class // heap_space okay
A_class // heap_space okay
A_class // heap_space deleted! will error when deconstructed

所以现在当我们退出时,我们试图删除index[3],结果一切都崩溃了!

通过添加一个使用swap的赋值运算符,我们可以解决这个问题:

class A_class
{
public:
//...
    // note the byval argument
    A_class& operator=(A_class other) {
        std::swap(this->heap_space, other.heap_space);
        return *this;
    }
//...
}

当您ObjArray.erase()一个对象时,std::vector<A_class>将填补产生的间隙。为此,它将把后面的对象向前移动一个对象:它分配给每个对象,直到所有对象都被分配,最后,它销毁最后一个元素,导致该对象的heap_space变为delete[]。但是,请注意,由于您没有复制或移动赋值,因此只分配了指针heap_space,即在erase()对象之后,最后一个对象处于错误状态:它持有指向bool的已delete[] ed数组的指针。当ObjArray后来超出范围时,所有对象都将被销毁,并发生双delete[]。这就是我们的调试断言。

解决问题的最简单方法是提供一个副本分配:

A_class& A_class::operator= (A_class other) {
    this->swap(other);
    return *this;
}
void A_class::swap(A_class& other) {
    std::swap(this->heap_space, other.heap_space);
}

上面的实现利用了您通常希望在所有实际需要副本分配的类中使用的三个操作:

  1. 在创建传递给赋值运算符的参数时,复制构造函数会处理创建已分配参数的新副本的情况
  2. swap()方法,它将当前对象的内容与变元中的临时副本进行交换
  3. 析构函数,在将原始左侧的内存放入临时副本时释放该内存