Natasha Drost
Natasha Drost

Reputation: 133

How does deletion of dynamic memory really work?

In main I have two types of objects: Person and Pet. A Person can have a number of Pets. I am creating both types of objects dynamically.

#include <iostream>
#include <string>
#include <vector>

struct Pet
{
    Pet() { }

    Pet(std::string name, std::string type)
    {
        Name = name;
        Type = type;
        std::cout << Name << " is created" << std::endl;
    }
    ~Pet()
    {
        std::cout << Name << " is destroyed" << std::endl;
    }

    std::string GetName() { return Name; }
    std::string GetType() { return Type; }

    std::string Name;
    std::string Type;
};

struct Person
{
    Person(std::string name)
    {
        Name = name;
        std::cout << Name << " is created" << std::endl;
    }

    ~Person()
    {
        std::cout << Name << " is destroyed" << std::endl;
    }

    void AddPet(Pet& pet)
    {
        Pets.push_back(pet);
    }

    std::string GetPersonsPets()
    {
        if (Pets.size() == 0)
        {
            return Name + " has no pets";
        }
        if (Pets.size() == 1)
        {
            return Name + " has " + Pets[0].GetName() + ",a " + Pets[0].GetType();
        }
    }

    std::string Name;
    std::vector<Pet> Pets;
};

int main()
{
    Person* peter = new Person("Peter");

    Person* alice = new Person("Alice");

    Pet* fluffy = new Pet("Flyffy", "dog");
    peter->AddPet(*fluffy);

    std::vector<Person*> People;
    People.push_back(peter);
    People.push_back(alice);

    int i = 0;
    for (std::vector<Person*>::iterator it = People.begin(); it != People.end();
        it++)
    {
        std::cout << People[i]->GetPersonsPets() << std::endl;
        i++;
    }
    //delete fluffy;
    delete alice;
    delete peter;
}

Everything seems to work as it should, but something interesting happens when it comes to deleting the Pet objects. When I uncomment delete fluffy, fluffy gets deleted by itself right after peter.

I thought that, since fluffy was created dynamically, it would never be destroyed unless I did that myself?

But the other interesting thing happens when I don't uncomment delete fluffy. Then it's destructor will be called twice.

How is it possible to destroy something twice?

Upvotes: 1

Views: 105

Answers (3)

nae9on
nae9on

Reputation: 369

Remember that what goes into a container is the copy of the object you specify. In your case, copy of the object *fluffy goes into the container. It is this copy that gets destroyed (thus calling its destructor) and not the original newed object created in this line Pet* fluffy = new Pet("Flyffy", "dog");. Thus, your code with //delete fluffy; results into a memory leak.

In case you consider copying the pointer itself into the container, note that when using containers of newed pointers, remember to delete the pointers before the container is destroyed (Item 7, Effective STL, Scott Meyers.) since destructing a pointer object does not call delete. Or else use smart_pointers as mentioned by @formerlyknownas_463035818

Upvotes: 0

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122133

When you add the pet here:

peter->AddPet(*fluffy);

then *fluffy is the pointer derferenced and here

void Person::AddPet(Pet& pet)
{
Pets.push_back(pet);
}

You store a copy of *fluffy in some container (I suppose it is a std::vector).

The objects you see getting destroyed are those copies (vector manages its own memory, ie it destroys its elements when the vector is destroyed). You still need to delete the dynamically allocated instances you created in main.

Note that you better didnt use new and delete at all here. Use dynamic memory when you must. And when you do you better use smart pointers rather than raw pointers.

PS Your code exhibits the bad practice of writing getters just because. All the members of Pet and Person are public. Either make the members private or remove the getters. Having both is misleading. Also if you do provide a default constuctor ( Pet() { }) it should initialize the members to meaningful values. The job of a constructor is to construct an object in a valid state. Last but not least you should use the member initializer list for constructors as in

Pet(std::string name, std::string type) : Name(name),Type(type) {}

Upvotes: 5

Yksisarvinen
Yksisarvinen

Reputation: 22176

Pets.push_back(pet); 

creates a copy of pet and stores it inside vector. When vector is destroyed, this copy is destroyed (but the original fluffy allocated with new is still leaked).

When you uncomment delete fluffy;, then you properly destroy both the original object and it's copy.

If you want to track copies of objects created, add user defined copy constructor and copy assignment operator (read up on The Rule of Three).

Upvotes: 3

Related Questions