【问题标题】:C++ Unexpected error when deleting objectsC++ 删除对象时出现意外错误
【发布时间】:2014-03-10 15:23:08
【问题描述】:

我写了一个解释器,其中每个关键字、语法符号或运算符都有Token的基类。

class Token {
    private:
        static std::vector<Token *> registered;

        size_t id;

        std::string name;
        std::string symbol;

    public:
        Token(const std::string& Name, const std::string& Symbol);
        Token::~Token();

        Token(const Token& rhs) = delete;
        Token& operator =(const Token& rhs) = delete;

        /* ... */

        static void DeleteRegistered();
};

构造函数

Token::Token(const std::string& Name, const std::string& Symbol)
                : name(Name), symbol(Symbol) {
    Token::registered.push_back(this);
    this->id = Token::registered.size();
}

析构函数

Token::~Token() {
    // Removes 'this' from Token::registered
    Token::registered.erase(std::remove(Token::registered.begin(), Token::registered.end(), this), Token::registered.end());
}

删除注册

void Token::DeleteRegistered() {
    for (size_t i = 0; i < Token::registered.size(); ++i) {
        delete Token::registered[i];
    }
}

在我的代码中,许多不同的类存储指向最终派生自 Token 的子类的指针容器。

为了避免删除对象两次或更多次,我存储了对所有分配实例的引用,并有一个静态方法将它们全部删除。

所有操作执行完毕后调用DeleteRegistered方法。

现在,我的问题:

当我调用Token::DeleteRegistered 时(在程序退出前几行发生,它会失败并且在调试中显示以下内容:

File: f:\dd\vctools\crt\crtw32\misc\dbgdel.cpp
Line: 52

Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)

由于所有Token 实例都没有真正定义的范围,因此我提出了这个设计,在我看来目前还可以。

什么会导致这个错误?

编辑: 析构函数是我后期添加的,将其注释掉仍然会显示上述错误。 delete 甚至无法删除容器中的第一项。

第二次编辑: 我如何使用Token的一个例子:

this->parser.Operators.Add(new RefBinaryOperator(
    "Assignment", "=", 14, RefBinaryOperator::Assign
));

注意:RefBinaryOperator 是Token 的子类(非直接),最终调用Token 的构造函数。

例如,我从Operators 容器中提取指向Tokens 的指针,并将它们分配给其他结构。一切完成后,我打电话给DeleteRegistered

最终编辑:

我通过将 Token 析构函数声明为 virtual 来使其工作:

Does delete work with pointers to base class?

【问题讨论】:

  • 由于每个令牌将在Token 生命周期内在该列表中注册/注销,您最终可能会删除在堆栈或另一个类中定义的令牌。另外,如何创建令牌?
  • 嗯,Token::DeleteRegistered() 不会删除大部分对象,但我不知道这会如何导致崩溃。您是否正在使用id 成员做任何事情?删除对象后,ids 将停止匹配,您将获得多个具有相同 ID 的实例...
  • 我不知道它是否会导致您的问题,但您需要一个虚拟析构函数。也无法确保所有实例都是用new 创建的;你如何确保所有这些都是可删除的?
  • In order to avoid deleting objects twice or more, I store references to all of the allocated instances, 也许您应该投资使用诸如 std::shared_ptr 之类的智能指针,而不是尝试跟踪实例数。使用 shared_ptr,一旦 remove() 完成它的工作,就是这样。你不需要那个静态成员数组。
  • @Dennis:“你能删除堆栈分配的对象吗?” - 不,你不能。这个设计要求所有东西都用new创建;这是失败的可能原因之一。

标签: c++ memory-management


【解决方案1】:

发生的情况是,在 Token::~Token() 中,您从 registered 向量中调用 erase,这会使您注意到问题的 for 循环中使用的索引无效。您需要记住,在该向量上调用 erase 后,需要正确调整 for 循环索引。如果您保留当前的析构函数,则以下内容可以在 DeleteRegistered 中工作:

void DeleteRegistered() {
  while(!Token::registered.empty())
    delete *Token::registered.begin();
}

此外,由于您有许多从Token 扩展的类,~Token() 应该变为虚拟,以便正确处理基类的销毁。

【讨论】:

  • 迭代是使用索引,而不是迭代器,所以它所做的唯一一件事就是未能删除一半的对象。但我不明白它是如何导致崩溃的。
  • 恐怕你的建议还是会出现错误。
  • @fasked:但是析构函数(由 delete 表达式调用)可以。
  • @fasked 在这种情况下你是不正确的,因为 Token 析构函数改变了向量的大小
  • 好的。我看到你的编辑。说真的,如果你使用 shared_ptr 或 shared_container,你的问题会大大减少,如果不是完全消失的话。
【解决方案2】:
void Token::DeleteRegistered() {
    for (size_t i = 0; i < Token::registered.size(); ++i) {
        delete Token::registered[i];
    }
}

上面不会做你想做的事。它将从registered 中删除第一个(第0 个)元素。第二个元素现在变成了第一个(第 0 个元素),但现在 i 将是 1。您的循环将只删除 Token::registered 中的所有其他元素。它泄漏了。

处理此问题的一种方法:在向量不为空时继续删除第一个或最后一个元素。我建议删除最后一个元素,因为这更符合向量的工作方式。删除第一个元素直到向量为空涉及每一步重建向量。

void Token::DeleteRegistered() {
    while (! Token::registered.empty()) {
        delete Token::registered.back();
    }
}

【讨论】:

  • remove 不会修改 registered (至少,不会以任何方式使迭代器无效),所以 erase 的参数的评估顺序无关紧要.
  • @MikeSeymour - 我的立场是正确的。我正在删除(并删除!)我答案的第一部分。问题出在DeleteRegistered,而不是Token::~Token
  • @DavidHammen 尝试您的解决方案,它确实设法删除了前两个 Token,但之后崩溃。
  • @Tyymo - 正如 PaulMcKenzie 几分钟前在他对您的问题的评论中所建议的那样,您应该向我们展示一个最小的工作示例。
  • 尽管您的建议不起作用,但它让我找到了我的代码的真正问题。谢谢
猜你喜欢
  • 2017-03-16
  • 2015-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多