删除对象时调试断言失败

时间:2013-12-02 21:04:41

标签: c++ object vector delete-operator erase

我最近遇到了一些用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);
}

我知道代码看起来不切实际,但我似乎无法弄清楚抛出异常的原因。代码将抛出“Debug Assertion Failed!”运行期间使用“Expression:_BLOCK_TYPE_IS_VALID(pHead-&gt; nBlockUse)”的消息,每次我尝试运行它。

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

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

2 个答案:

答案 0 :(得分:2)

有趣的是,这是因为您没有提供赋值运算符。你为什么需要一个?好吧,让我们考虑一个向量。 vector对象有一个指向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;
    }
//...
}

答案 1 :(得分:0)

当您ObjArray.erase()某个对象时,std::vector<A_class>将填补所产生的差距。为此,它会将后一个对象向前移动一个对象:为每个对象分配,直到分配了所有对象,最后,它会销毁最后一个元素,从而导致delete[]对象的heap_space。但请注意,由于您没有复制或移动赋值,因此只指定了heap_space指针,即在erase()对象之后,最后一个对象处于错误状态:它持有指针已经delete[]已编辑的bool数组。当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. 析构函数释放原始左侧的内存,因为它被放入临时副本。