1

I have such code like,

#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);
}

Then I run my code, an error(Debug Assertion Failed!) pops up. And you can also see the destructor is called twice. But if I remove the delete [] k; in the ~Person, there will be no such pop-up error.

There is dynamic allocation in the Person constructor:

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

So I think I should delete k in the destructor. My question is why the destructor is called twice and how to solve the error?

I am using VS 2013.

Thank you!

Jack
  • 13
  • 4
  • possible duplicate of [What is The Rule of Three?](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – Sneftel May 14 '15 at 22:13
  • 1
    use vector to avoid such problems – Gabriel May 14 '15 at 22:13
  • @Sneftel Yes, it's caused by missing copy constructor – StenSoft May 14 '15 at 22:14
  • Add a copy constructor/assignemnt operation with a print statements in there. And watch as a copy of your object is created. Turn on optimization all the way and watch as both the extra copy and delete are removed. Note: If you don't define them the compiler automatically generates them with no print statement. – Martin York May 14 '15 at 22:17

3 Answers3

5

The issue is the following. In the line

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

you are copy-initializing p, i.e. you are creating a temporary which is then copied to Person p;. At the end, the temporary Person("Jack", "Bowen", 24, 60, 0.99, 0.98); is destroyed, so your Kidney* pointer is dangling, because you didn't implement a copy constructor and the copy is shallow (i.e. the pointer itself is being copied, not the object it points to). And your destructor is called twice because it is first called when the temporary ends its life (at the end of the statement), then again when Person p goes out of scope at the end of main().

Anytime your class has a pointer, implement its copy constructor and assignment operator. Or better, use smart pointers like std::shared_ptr, or even better, standard containers that keep track of their dynamic memory like std::vector/std::list etc.

Quick and dirty fix for your code (but really, you must implement the copy constructor since you're going to have all other kinds of issues, e.g. when returning Persons from a function or when passing Persons by value):

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

This avoids any temporary and uses direct initialization.

PS: In g++, compiling with -Weffc++ warns you about these issues,

warning: 'class Person' has pointer data members [-Weffc++] but does not override 'Person(const Person&)' [-Weffc++] or 'operator=(const Person&)' [-Weffc++]

I am not sure if such a compiler flag exists for VS though.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • So the reason that this error occurs is because I delete the same pointer twice, right? What if I write: ' ~Person() { if (k != nullptr) delete[] k; }' Can this avoid re-delete the same pointer? – Jack May 14 '15 at 23:08
  • @Jack no, because the copy is being made when the pointer is still valid, before the destruction of the temporary. – vsoftco May 15 '15 at 22:51
4

The problem is with your line

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

This constructs two objects: one to the right of the =, and one to the left. Since you did not define a copy constructor, the one to the left will simply copy the exact same pointer of the one to the right. The two destructors you mention are of these two objects, and the one to the left of the = is the one causing the manifestation of your problem.

In order to address this, you can do one of the following:

  1. Correctly define a copy constructor which will not copy the pointer, but rather allocate a new pointer, copy the internal objects, etc.

  2. A better way would be to replace the pointer with a ready-made class which does these things for you, e.g., vector.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
0

As mentioned before add a copy constructor/assignemnt operation whould be ok. But if you just want to resolve this problem, use pointer will be easy.

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