【问题标题】:[c++]Why is my class destructor called twice?[c++]为什么我的类析构函数被调用了两次?
【发布时间】:2015-07-26 17:12:54
【问题描述】:

我有这样的代码,

#include <iostream>
#include <string>

using namespace std;

class Heart {
private:
    int bpm;
public:
    Heart(int bpm) : bpm(bpm) {}
    int getBPM() {
        return bpm;
    }
};

class Kidney {
private:
    double PercentFunction;
public:
    Kidney() : PercentFunction(0) {}
    Kidney(double pf) : PercentFunction(pf) {}
    double getPF() {
        return PercentFunction;
    }
};

class Person {
private:
    string fname, lname;
    int age;
    Heart h;
    Kidney* k; 

public:
    Person(string fn, string ln, int age, int bpm, double kpf1, double kpf2) : fname(fn), lname(ln), age(age), h(bpm) {
        k = new Kidney[2];
        k[0] = Kidney(kpf1);
        k[1] = Kidney(kpf2);
        cout << fname << " " << lname << ", aged " << age << ". Heart BPM : " << bpm <<
            ". Kidneys' percent function indices: " << k[0].getPF() << " and " << k[1].getPF() << '.' << endl;
    }
    ~Person() {
        cout << "A person is dying!" << endl;
        delete[] k;
    }


};


int main() {
    Person p = Person("Jack", "Bowen", 24, 60, 0.99, 0.98);
}

然后我运行我的代码,一个错误(调试断言失败!)弹出。您还可以看到析构函数被调用了两次。但是如果我把~Person里面的delete [] k;去掉,就不会出现这样的弹窗错误了。

Person构造函数中有动态分配:

k = new Kidney[2];
k[0] = Kidney(kpf1);
k[1] = Kidney(kpf2);

所以我认为我应该在析构函数中删除 k 。 我的问题是为什么会调用两次析构函数以及如何解决错误?

我正在使用 VS 2013。

谢谢!

【问题讨论】:

  • What is The Rule of Three? 的可能重复项
  • 使用矢量来避免此类问题
  • @Sneftel 是的,这是由于缺少复制构造函数引起的
  • 添加一个复制构造函数/assignemnt 操作,其中包含打印语句。并观察创建对象的副本。一直打开优化,观察多余的复制和删除都被删除了。注意:如果你没有定义它们,编译器会自动生成它们而不需要打印语句。

标签: c++ class visual-studio-2013 destructor


【解决方案1】:

问题如下。在行中

Person p = Person("Jack", "Bowen", 24, 60, 0.99, 0.98);

您正在复制初始化p,即您正在创建一个临时文件,然后将其复制到Person p;。最后,临时的Person("Jack", "Bowen", 24, 60, 0.99, 0.98); 被销毁,所以你的Kidney* 指针悬空,因为你没有实现复制构造函数并且复制很浅(即指针本身被复制,而不是它指向的对象)。并且你的析构函数被调用了两次,因为它在临时结束时(在语句末尾)第一次被调用,然后在Person pmain() 结束时超出范围时再次调用。

只要你的类有一个指针,实现它的复制构造函数和赋值运算符。或者更好的是,使用像std::shared_ptr 这样的智能指针,或者甚至更好的标准容器来跟踪它们的动态内存,比如std::vector/std::list 等。

快速而肮脏地修复您的代码(但实际上,您必须实现复制构造函数,因为您将遇到所有其他类型的问题,例如从函数返回 Persons 或传递 Persons 时按值):

Person p("Jack", "Bowen", 24, 60, 0.99, 0.98);

这避免了任何临时性并使用直接初始化。

PS:在g++ 中,使用-Weffc++ 编译会警告您这些问题,

警告:'class Person' 具有指针数据成员 [-Weffc++] 但不会覆盖 'Person(const Person&)' [-Weffc++] 或 'operator=(const Person&)' [-Weffc++]

我不确定 VS 是否存在这样的编译器标志。

【讨论】:

  • 所以出现这个错误的原因是因为我两次删除了同一个指针,对吧?如果我写:' ~Person() { if (k != nullptr) delete[] k; }' 这样可以避免重新删除同一个指针吗?
  • @Jack 不,因为当指针仍然有效时,在临时销毁之前,正在制作副本。
【解决方案2】:

问题在于你的线路

Person p = Person("Jack", "Bowen", 24, 60, 0.99, 0.98);

这构造了 两个 对象:一个在= 的右侧,一个在左侧。由于您没有定义复制构造函数,因此左侧的指针将简单地复制与右侧指针完全相同的指针。你提到的两个析构函数就是这两个对象的,=左边的那个是导致你的问题表现出来的那个。

为了解决这个问题,您可以执行以下操作之一:

  1. 正确定义一个不会复制指针的复制构造函数,而是分配一个新的指针,复制内部对象等

  2. 更好的方法是将指针替换为为您执行这些操作的现成类,例如 vector

【讨论】:

    【解决方案3】:

    如前所述,添加一个复制构造函数/assignemnt 操作应该没问题。但是如果你只是想解决这个问题,使用指针会很容易。

    int main() {
        Person *p = new Person("Jack", "Bowen", 24, 60, 0.99, 0.98);
    }
    

    【讨论】:

      猜你喜欢
      • 2013-01-12
      • 2011-02-07
      • 2017-11-03
      • 2021-11-06
      • 1970-01-01
      • 2013-11-24
      • 2020-02-23
      • 1970-01-01
      相关资源
      最近更新 更多