C++:如何结合指针为向量编写析构函数

c++: how to write a destructor for a vector combined with a pointer

本文关键字:指针 向量 析构函数 结合 何结合 C++      更新时间:2023-10-16

我需要为学校的练习编写一个析构函数。我已经尝试为 A 类编写析构函数,这是正确还是错误的方式?

纸上练习

该练习说析构函数需要像这样开始:

A *pa = .......;
delete pa;

4个类的代码:

Class A
{
  private:
    vector <B*> b;
    vector <C*> c;
  public:
    ~A();
}
Class B
{
  private:
    vector <D*> d;
  public:
    ~B();
}
Class C
{
  private:
    vector <D*> d;
  public:
    ~C();
}
Class D
{
  private:
    vector <A*> a;
  public:
    ~D();
}

我已经尝试为类 A 编写析构函数,这是正确的方法吗?

~A()
{
  for (int i = 0; i < b.size(); i++)
  {
     B* pa = b[i];
     delete pa;
  }
  for (int j = 0; j < c.size(); i++)
  {
    C* pa = c[i];
    delete pa;
  }

假设您不确定类 A 中两个vector中的指针是否都不是NULL,那么您可以执行以下操作:

~A()
{
  for (int i = 0; i < b.size(); i++)
    delete b[i];
  b.clear(); // Making sure you do not access to deleted pointers
  for (int i = 0; i < c.size(); i++)
    delete c[i];
  c.clear();  // Making sure you do not access to deleted pointers
}

我们还清除了向量,以确保无法访问已删除的点。或者,如果您仍然需要矢量的大小,您可以设置 b[i] = nullptrc[i] = nullptr在两个循环中,而不清除向量。

这取决于构造函数的作用。

一般来说,构造函数建立一个类不变(所有成员函数

都可以假定为真的一组属性),类外部使用的所有成员函数(例如公共成员,为派生类提供服务的受保护成员)保持该不变性。 析构函数执行与构造函数相反的操作 - 例如,如果构造函数显式分配某些资源,析构函数会释放它。

如果该不变量包括"b的所有元素都被分配了运算符new的结果",则析构函数必须使用(相应的运算符delete释放这些元素,或者交给这样做的其他对象。 这通常被描述为所有权 - 类A执行影响其他对象生存期的操作,因此必须清理。

如果该不变性包括"c的所有元素要么是 NULL 指针,要么包含从其他地方提供的对象的地址",则析构函数不应释放这些对象。 例如,一些其他代码可能会提供指向 A 的成员函数的指针,并且该指针只是放在 c 中。 这假设您知道这些对象的生存期是在类A之外管理的。

例如,考虑一个执行此操作的构造函数

 A::A(int num_b, const std::vector<C *> &in_c) : b(0), c(in_c)
 {
     b.reserve(num_b);    // so b doesn't keep on resizing itself below
     for (int i = 0; i < num_b; ++i)
        b.push_back(new B);
 }

这样做的逻辑是,A分配所有地址存储在b中的对象,但只是从in_c复制指针(这些指针可能在其他地方管理)。 所以类 A 负责 b 中对象的生存期,而不是c中的对象的生存期。

然后我们假设,如果成员函数更改bc,则在调用A析构函数时仍然适用 - b中的所有对象都由类 A 管理,但 c 中的对象则不然。 (换句话说,我们假设保持类不变性)。

那么析构函数可能看起来像

 A::~A()
 {
     for (std::vector<B *>::iterator i = b.begin(), end = b.end();
          i != end;
          ++i)
     {
          delete (*i);
     }
 }

注意我们不需要调整bc的大小,因为std::vector的析构函数负责清理自己。 但是,由于我们的类分配了对象并将其地址存储在b中 - std::vector对此一无所知 - 析构函数必须释放它们。

注意:上面的示例适用于所有版本的C++。 在 C++11 之后,析构函数中的代码可以简化,例如使用新的样式循环或auto类型推导。

这取决于。如果A拥有向量中的指针,因此负责销毁指向的对象,并且对象是使用 new 创建的,那么是的,析构函数看起来确实是正确的。

如果指向的对象不是用new创建的,或者指针不是A所有的,那么A的析构函数不应该删除它们。

如果A确实拥有指针,那么在类A中分配对象将是糟糕的设计。但我认为,为了简单起见,这被排除在示例之外。

作为旁注:如果A确实拥有指针并在析构函数中删除它们,则还应实现复制构造函数和复制赋值运算符,以便它们进行深层复制或使类不可复制。否则,如果您复制实例,您最终将出现未定义的行为。

但是正如您在 B 类和 C 类中看到的那样,我去了 D 类,但是如果我删除 D 会出错吗?

这样做完全没问题,除非...

。如果A拥有指向BC的指针,并且BC拥有指向D的指针,并且D拥有指向A的指针,以便它们都删除指向的对象,那么在类级别存在所有权循环。那还是没问题的,除非...

。有一个指针循环。例如,D 的一个实例(我们称之为 dee )指向一个指向deeB实例的A实例,然后你有一个循环,你最终删除了一个对象,其析构函数已经启动,这会导致未定义的行为。

因此,如果这种指针循环是可能的,那么必须避免所有权循环。但是,如果不能有指针循环,那么所有权循环可能存在。