析构函数调用和指针

Destructor call and pointers

本文关键字:指针 函数调用 析构      更新时间:2023-10-16

这是我的问题假设我有一个函数,它由这样的东西组成:

void function() {  
    entity e; //entity is just a class  
    entities.push_back(e); //entities is a vector of entity objects  
}

这就是困扰我的事情。"e"包含指向另一个对象的指针。调用实体的析构函数时,它会删除该指针。由于"e"和实体中的实体都指向同一位置,如果我从实体(在函数外部)对该指针执行某些操作,它会给出错误,因为一旦函数返回,指针上的任何内容都会被删除。解决此问题的最佳方法是什么?

C++是一种"基于复制"的语言,例如,Entity的容器确实会将您提供给它的实体的副本放在容器中。

副本在许多地方都是C++制作的,所以最好是你的班级正确支持它们,或者你完全禁止它们。

您的类包含指向其他数据的指针:当您复制该类的实例时会发生什么?如果复制指针是可以的,那么显然不能删除析构函数中的指向对象,因为仍然存在的副本将指向已删除的对象。

有一个简单的规则可以帮助避免这种错误,被称为"三法则"。如果您已显式编码

  • 复制构造函数
  • 析构函数
  • 赋值运算符

在你的课堂上,很可能你需要所有三个。

在这种情况下,您

有一个不是默认析构函数的析构函数(因为删除了指向的对象),因此您还需要告诉在复制构造或赋值的情况下该怎么做。

如果您希望该类不可复制,则只需确保

struct Entity {
    Object *o;
    Entity(Object *o) : o(o) {
        ...
    }
    ~Entity() {
        delete o;
    }
private:
    // Taboo... this should just never happen!!!
    // Here is a declaration, but no implementation will be written
    Entity(const Entity& other);      // Copy constructor
    Entity& operator=(const Entity&); // Assignment
};

声明禁止的操作private将确保用户代码永远不会调用它们(这将是一个编译时错误),并且仅声明它们而不提供实现将确保即使是类代码本身也不会错误地调用它们(你会得到一个链接时错误)。

但是,在这种特定情况下,这将禁止您的代码将Entity实例放入容器中(必须复制容器内的元素)。您可以将Entity指针放在容器中(可以复制指针,以便entities std::vector<Entity *>是合法的),但您将负责处理对象的正确生存期(谁应该调用析构函数以及何时发生?

另一方面,如果你有一个指向类内部数据的指针,并且你想要允许创建类实例的副本,你可以:

  • 同时复制指向的数据
  • 在不同实例之间共享指向数据

对于第二种解决方案,一种常见的方法是使用"引用计数"指针,即指向的数据"知道"有多少指针引用它,并且仅当该计数达到 0 时才被销毁。

更改指向共享对象的指针以包含shared_ptr将是一个简单的解决方案。

这是因为你违反了三法则:如果你有一个析构函数,你几乎肯定也需要有一个复制构造函数和一个赋值运算符。

当然,处理指针的最好方法是找到一种完全没有指针的方法("零规则"):在这种情况下,编译器生成的析构函数、构造函数和赋值运算符会负责自动为您管理资源。

如果实体类具有析构函数,则还应按三规则定义复制和复制赋值运算符。

不幸的是,这可能意味着您必须复制对象中指向的项目,以便在销毁对象的副本时不会删除稍后将被其他副本删除的项目。 "不幸的是",因为创建指向项的副本通常是不可取的;在这种情况下,你有一个设计难题 - 尽管Tom Kerr的建议WRT shared_ptr是一个不错的选择。

另一种解决方案是禁止复制,方法是将复制构造函数设为私有,或者 (C++11) 通过使用 = delete ,并entity一个指针数组代替(和 push_back(&e))。