【问题标题】:C++ Explanation for this delete[] Error?此 delete[] 错误的 C++ 解释?
【发布时间】:2014-03-28 23:58:08
【问题描述】:

在对项目进行了大量更改后,我创建了一个错误,我花了很长时间才找到。

我有一个包含动态分配数组的类。然后我创建这个类的动态数组。然后我可以删除[]那个数组。但是,如果我在删除之前替换数组中的某个项目,则会导致错误。在调试模式下,它会给出来自 dbgdel.cpp 的断言消息“表达式:_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)”。这是一个小程序来演示。

class SomeClass {
public:
    int *data;

    SomeClass() {
        data = nullptr;
    }
    SomeClass(int num) {
        data = new int[num];
    }
    ~SomeClass() {
        if (data != nullptr) { 
            delete[] data;
        }
    }
};

int main(int argc, char *args[]) {
    SomeClass *someArray = new SomeClass[10];

    //If you comment this out, there is no error.  Error gets thrown when deleting
    someArray[0] = SomeClass(10);

    delete[] someArray;
    return 0;
}

我很好奇,为什么会这样?当数组中的项目被替换时,它的析构函数被调用。然后,新项目将其数据分配到与数组分开的位置。然后 delete[] 调用数组中所有对象的析构函数。当调用析构函数时,它们应该删除项目的数据数组。我无法想象问题出在哪里,但我希望有人能解释一下。

【问题讨论】:

  • +1 用于创建最小的测试用例 ;)
  • if (data != nullptr) 是僵尸编程的例子。
  • +1 以良好的方式提出问题。我的建议是尽可能多地使用智能指针、STL 容器和 RAII,这样一开始就不会遇到这类问题。
  • OliCharlesworth 是的,我认为其他人和我一样讨厌阅读不相关的材料 :) KerrekSB 我实际上并没有这样做。为了清楚起见,我只是添加了它。
  • @JanzDott:这不是“清晰”的含义:-S ...这就像说return x 不如return x == true ? true : false 清晰。

标签: c++ arrays dynamic delete-operator


【解决方案1】:

Kerrek SB 的回答很棒。我只是想澄清一下,在您的代码中,内存被释放了两次。

这段代码

someArray[0] = SomeClass(10);

和这个一样

SomeClass temp(10);
someArray[0] = temp; //here temp.data is copied to someArray[0].data

然后为 temp 调用 ~SomeClass() 并首次释放 data

这里

delete[] someArray;

~SomeClass() 为 someArray[0] 调用,data 第二次被释放。

【讨论】:

    【解决方案2】:

    你的类坏了:它有一个非平凡的析构函数,但你没有定义复制构造函数和复制赋值运算符。这意味着无法正确复制或分配类(因为未正确复制或分配可破坏状态),正如您在示例代码中注意到的那样。

    你可以让你的类不可复制(在这种情况下你的代码将不再编译),或者move-only,在这种情况下你需要定义移动构造和移动分配,或者通过实现数据的深层副本来正确复制。

    方法如下,添加以下定义:

    不可复制:

    SomeClass(SomeClass const &) = delete;
    SomeClass & operator(SomeClass const &) = delete;
    

    仅可移动:

    SomeClass(SomeClass const &) = delete;
    SomeClass(SomeClass && rhs) : data(rhs.data) { rhs.data = nullptr; }
    SomeClass & operator(SomeClass const &) = delete;
    SomeClass & operator(SomeClass && rhs) {
        if (this != &rhs) { delete data; data = rhs.data; rhs.data = nullptr; }
        return *this;
    }
    

    可复制:

    SomeClass(SomeClass const & rhs) : ptr(new int[rhs->GetSizeMagically()]) {
        /* copy from rhs.data to data */
    }
    SomeClass & operator(SomeClass const & rhs) {
        if (this == &rhs) return *this;
    
        int * tmp = new int[rhs->GetSizeMagically()];
        /* copy data */
        delete data;
        data = tmp;
    }
    // move operations as above
    

    结果是析构函数的性质决定了类的不变量,因为每个对象都必须始终可破坏。从中可以推断出复制和移动操作所需的语义。 (这通常被称为三法则或五法则。)

    【讨论】:

    • 是的;规范的有用链接是What is the rule of three?
    • 嗯,有道理。我以前从未为复制或赋值运算符而烦恼。很高兴知道。我有很多课程,我可能应该将它们添加到其中。
    • @JanzDott:可能不是——C++ 的真正规则是零规则:不要自己编写any,而是将问题分解到单个类中负责为您管理资源的职责(例如std::vector<int>)。如果您发现自己在编写析构函数,则本质上应该将其视为设计过程的失败,除非您戴着“库维护者”的帽子。
    • @KerrekSB 我避免 std::vector 用于琐碎的事情,因为它在调试模式下非常慢。通常这不会是一个问题。但它是在您处理诸如 3D 游戏引擎或光线追踪器之类的性能关键问题时。 std::vector 是我的游戏引擎在调试模式下从 800+ fps 下降到爬行滞后的原因。当我进行分析时,这是一个大问题。
    • @JanzDott:我明白了。也许尝试不同的编译器? GCC 有一个-Og 模式,现在它是可调试的,但也有一些优化......一般的格言是,快速正确的代码比快速正确的代码更容易,如果你不能处理“手工”编写代码的复杂性(而且我们在这里只展示了最微不足道的复杂性,这基本上已经杀死了你),那么以优化为目标可能是有问题的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 2012-01-19
    • 2017-03-20
    • 1970-01-01
    • 2013-06-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多